/*
 * Ext Gantt 1.2
 * Copyright(c) 2009-2011 Mats Bryntse Consulting
 * 
 * info@ext-scheduler.com
 * 
 * http://www.ext-scheduler.com/license.html
 * 
 */



Ext.ns('Ext.ux.maximgb.tg');

Ext.ux.maximgb.tg.AbstractTreeStore = Ext.extend(Ext.data.Store,
{
    
    leaf_field_name : 'IsLeaf',
    
    
    page_offset : 0,
    
    
    active_node : null,
    
    defaultExpanded : false,
    
    
    constructor : function(config)
    {
        Ext.ux.maximgb.tg.AbstractTreeStore.superclass.constructor.call(this, config);
        
        if (!this.paramNames.active_node) {
            this.paramNames.active_node = 'anode';
        }
        
        this.addEvents(
            
            'beforeexpandnode',
            
            'expandnode',
            
            'expandnodefailed',
            
            'beforecollapsenode',
            
            'collapsenode',
            
            'beforeactivenodechange',
            
            'activenodechange'
        );
    },  

    // Store methods.
    // -----------------------------------------------------------------------------------------------  
    
    remove : function(record)
    {
        // ----- Modification start
        if (record === this.active_node) {
            this.setActiveNode(null);
        }
        this.removeNodeDescendants(record);
        // ----- End of modification        
        Ext.ux.maximgb.tg.AbstractTreeStore.superclass.remove.call(this, record);
    },
    
    
    removeNodeDescendants : function(rc)
    {
        var i, len, children = this.getNodeChildren(rc);
        for (i = 0, len = children.length; i < len; i++) {
            this.remove(children[i]);
        }
    },
    
    
    load : function(options)
    {
        if (options) {
            if (options.params) {
                if (options.params[this.paramNames.active_node] === undefined) {
                    options.params[this.paramNames.active_node] = this.active_node ? this.active_node.id : null;
                }
            }
            else {
                options.params = {};
                options.params[this.paramNames.active_node] = this.active_node ? this.active_node.id : null;
            }
        }
        else {
            options = {params: {}};
            options.params[this.paramNames.active_node] = this.active_node ? this.active_node.id : null;
        }

        if (options.params[this.paramNames.active_node] !== null) {
            options.add = true;
        }

        return Ext.ux.maximgb.tg.AbstractTreeStore.superclass.load.call(this, options); 
    },
    
    
    loadRecords : function(o, options, success)
    {
        if (!o || success === false) {
            if (success !== false) {
                this.fireEvent("load", this, [], options);
            }
            if (options.callback) {
                options.callback.call(options.scope || this, [], options, false);
            }
            return;
        }
    
        var r = o.records, t = o.totalRecords || r.length,  
            page_offset = this.getPageOffsetFromOptions(options),
            loaded_node_id = this.getLoadedNodeIdFromOptions(options), 
            loaded_node, i, len, record, idx, updated, self = this;
    
        if (!options || options.add !== true) {
            if (this.pruneModifiedRecords) {
                this.modified = [];
            }
            for (var i = 0, len = r.length; i < len; i++) {
                r[i].join(this);
            }
            if (this.snapshot) {
                this.data = this.snapshot;
                delete this.snapshot;
            }
            this.data.clear();
            this.data.addAll(r);
            this.page_offset = page_offset;
            this.totalLength = t;
            this.applySort();
            this.fireEvent("datachanged", this);
        } 
        else {
            if (loaded_node_id) {
                loaded_node = this.getById(loaded_node_id);
            }
            if (loaded_node) {
                this.setNodeChildrenOffset(loaded_node, page_offset);
                this.setNodeChildrenTotalCount(loaded_node, Math.max(t, r.length));
                this.removeNodeDescendants(loaded_node);
            }
            this.suspendEvents();
            updated = {};
            for (i = 0, len = r.length; i < len; i++) {
                record = r[i];
                idx = this.indexOfId(record.id);
                if (idx == -1) {
                    updated[record.id] = false;
                }
                else {
                    updated[record.id] = true;
                    this.setNodeExpanded(record, this.isExpandedNode(this.getAt(idx)));
                }
                this.add(record);
            }
            this.applySort();
            this.resumeEvents();
    
            r.sort(function(r1, r2) {
                var idx1 = self.data.indexOf(r1),
                    idx2 = self.data.indexOf(r2),
                    r;
         
                if (idx1 > idx2) {
                   r = 1;
                }
                else {
                   r = -1;
                }
                return r;
            });
            
            var added = [];
            for (i = 0, len = r.length; i < len; i++) {
                record = r[i];
                if (updated[record.id] == true) {
                    this.fireEvent('update',  this, record, Ext.data.Record.COMMIT);
                }
                else {
                    if (loaded_node_id) {
                        added.push(record);
                    } else {
                        this.fireEvent("add", this, [record], this.data.indexOf(record));
                    }
                }
            }
            
            // If this is a node expand, only fire one add event
            if (loaded_node_id && added.length > 0) {
                this.fireEvent("add", this, added, this.data.indexOf(added[0]));
            }
        }
        this.fireEvent("load", this, r, options);
        if (options.callback) {
            options.callback.call(options.scope || this, r, options, true);
        }
    },

   
    sort : function(fieldName, dir)
    {
        if (this.remoteSort) {
            this.setActiveNode(null);
            if (this.lastOptions) {
                this.lastOptions.add = false;
                if (this.lastOptions.params) {
                    this.lastOptions.params[this.paramNames.active_node] = null;
                }
            }
        }

        return Ext.ux.maximgb.tg.AbstractTreeStore.superclass.sort.call(this, fieldName, dir);         
    },    

    
    applySort : function()
    {
        if(this.sortInfo && !this.remoteSort){
            var s = this.sortInfo, f = s.field;
            this.sortData(f, s.direction);
        }
        // ----- Modification start
        else {
            this.applyTreeSort();
        }
        // ----- End of modification
    },
    
    
    sortData : function(f, direction) 
    {
        Ext.ux.maximgb.tg.AbstractTreeStore.superclass.sortData.apply(this, arguments);
        // ----- Modification start
        this.applyTreeSort();
        // ----- End of modification
    },
    
    // Tree support methods.
    // -----------------------------------------------------------------------------------------------

    
    applyTreeSort : function()
    {
        var i, len, temp,
                rec, records = [],
                roots = this.getRootNodes();
                
        // Sorting data
        for (i = 0, len = roots.length; i < len; i++) {
            rec = roots[i];
            records.push(rec);
            this.collectNodeChildrenTreeSorted(records, rec); 
        }
        
        if (records.length > 0) {
            this.data.clear();
            this.data.addAll(records);
        }
        
        // Sorting the snapshot if one present.
        if (this.snapshot && this.snapshot !== this.data) {
            temp = this.data;
            this.data = this.snapshot;
            this.snapshot = null; 
            this.applyTreeSort();
            this.snapshot = this.data;
            this.data = temp;
        }
    },
    
    
    collectNodeChildrenTreeSorted : function(records, rec)
    {
        var i, len,
            child, 
            children = this.getNodeChildren(rec);
                
        for (i = 0, len = children.length; i < len; i++) {
            child = children[i];
            records.push(child);
            this.collectNodeChildrenTreeSorted(records, child); 
        }
    },
    
    
    getActiveNode : function()
    {
        return this.active_node;
    },
    
    
    setActiveNode : function(rc)
    {
        if (this.active_node !== rc) {
            if (rc) {
                if (this.data.indexOf(rc) != -1) {
                    if (this.fireEvent('beforeactivenodechange', this, this.active_node, rc) !== false) {
                        this.active_node = rc;
                        this.fireEvent('activenodechange', this, this.active_node, rc);
                    }
                }
                else {
                    throw "Given record is not from the store.";
                }
            }
            else {
                if (this.fireEvent('beforeactivenodechange', this, this.active_node, rc) !== false) {
                    this.active_node = rc;
                    this.fireEvent('activenodechange', this, this.active_node, rc);
                }
            }
        }
    },
     
    
    isExpandedNode : function(rc)
    {
        if (this.defaultExpanded) {
            return rc.ux_maximgb_tg_expanded !== false;
        } else {
            return rc.ux_maximgb_tg_expanded === true;
        }
    },
    
    
    setNodeExpanded : function(rc, value)
    {
        rc.ux_maximgb_tg_expanded = value;
    },
    
    
    isVisibleNode : function(rc)
    {
        var i, len,
                ancestors = this.getNodeAncestors(rc),
                result = true;
        
        for (i = 0, len = ancestors.length; i < len; i++) {
            result = result && this.isExpandedNode(ancestors[i]);
            if (!result) {
                break;
            }
        }
        
        return result;
    },
    
    
    isLeafNode : function(rc)
    {
        return rc.get(this.leaf_field_name) == true;
    },
    
    
    isLoadedNode : function(rc)
    {
        var result = false;
        
        if (rc.ux_maximgb_tg_loaded !== undefined) {
            result = rc.ux_maximgb_tg_loaded;
        }
        else if (this.hasChildNodes(rc) || this.isLeafNode(rc)) {
            result = true;
        }
        
        return result;
    },
    
    
    setNodeLoaded : function(rc, value)
    {
        rc.ux_maximgb_tg_loaded = value;
    },
    
    
    getNodeChildrenOffset : function(rc)
    {
        return rc.ux_maximgb_tg_offset || 0;
    },
    
    
    setNodeChildrenOffset : function(rc, value)
    {
        rc.ux_maximgb_tg_offset = value;
    },
    
    
    getNodeChildrenTotalCount : function(rc)
    {
        return rc.ux_maximgb_tg_total || 0;
    },
    
    
    setNodeChildrenTotalCount : function(rc, value)
    {
        rc.ux_maximgb_tg_total = value;
    },
    
    
    collapseNode : function(rc)
    {
        if (
            this.isExpandedNode(rc) &&
            this.fireEvent('beforecollapsenode', this, rc) !== false 
        ) {
            this.setNodeExpanded(rc, false);
            this.fireEvent('collapsenode', this, rc);
        }
    },
    
    
    expandNode : function(rc)
    {
        var params;
        
        if (!this.isExpandedNode(rc) &&
             this.fireEvent('beforeexpandnode', this, rc) !== false
        ) {
            // If node is already loaded then expanding now.
            if (this.isLoadedNode(rc)) {
                this.setNodeExpanded(rc, true);
                this.fireEvent('expandnode', this, rc, false);
            }
            // If node isn't loaded yet then expanding after load.
            else {
                this.fireEvent('expandingnode', this, rc);
                params = {};
                params[this.paramNames.active_node] = rc.id;
                this.load({
                    add : true,
                    params : params,
                    callback : this.expandNodeCallback,
                    scope : this
                });
            }
        }
    },
    
    
    expandNodeCallback : function(r, options, success)
    {
        var rc = this.getById(options.params[this.paramNames.active_node]);
        
        if (success && rc) {
            this.setNodeLoaded(rc, true);
            this.setNodeExpanded(rc, true);
            this.fireEvent('expandnode', this, rc, true);
        }
        else {
            this.fireEvent('expandnodefailed', this, options.params[this.paramNames.active_node], rc);
        }
    },
    
    
    expandAll : function()
    {
        var r, i, len, records = this.data.getRange();
        this.suspendEvents();
        for (i = 0, len = records.length; i < len; i++) {
            r = records[i];
            if (!this.isExpandedNode(r)) {
                this.expandNode(r);
            }
        }
        this.resumeEvents();
        this.fireEvent('datachanged', this);
    },
    
    
    collapseAll : function()
    {
        var r, i, len, records = this.data.getRange();
        
        this.suspendEvents();
        for (i = 0, len = records.length; i < len; i++) {
            r = records[i];
            if (this.isExpandedNode(r)) {
                this.collapseNode(r);
            }
        }
        this.resumeEvents();
        this.fireEvent('datachanged', this);
    },
    
    
    getLoadedNodeIdFromOptions : function(options)
    {
        var result = null;
        if (options && options.params && options.params[this.paramNames.active_node]) {
            result = options.params[this.paramNames.active_node];
        }
        return result;
    },
    
    
    getPageOffsetFromOptions : function(options)
    {
        var result = 0;
        if (options && options.params && options.params[this.paramNames.start]) {
            result = parseInt(options.params[this.paramNames.start], 10);
            if (isNaN(result)) {
                result = 0;
            }
        }
        return result;
    },
    
    // Public
    hasNextSiblingNode : function(rc)
    {
        return this.getNodeNextSibling(rc) !== null;
    },
    
    // Public
    hasPrevSiblingNode : function(rc)
    {
        return this.getNodePrevSibling(rc) !== null;
    },
    
    // Public
    hasChildNodes : function(rc)
    {
        return this.getNodeChildrenCount(rc) > 0;
    },
    
    // Public
    getNodeAncestors : function(rc)
    {
        var ancestors = [],
            parent;
        
        parent = this.getNodeParent(rc);
        while (parent) {
            ancestors.push(parent);
            parent = this.getNodeParent(parent);    
        }
        
        return ancestors;
    },
    
    // Public
    getNodeChildrenCount : function(rc)
    {
        return this.getNodeChildren(rc).length;
    },
    
    // Public
    getNodeNextSibling : function(rc)
    {
        var siblings,
            parent,
            index,
            result = null;
                
        parent = this.getNodeParent(rc);
        if (parent) {
            siblings = this.getNodeChildren(parent);
        }
        else {
            siblings = this.getRootNodes();
        }
        
        index = siblings.indexOf(rc);
        
        if (index < siblings.length - 1) {
            result = siblings[index + 1];
        }
        
        return result;
    },
    
    // Public
    getNodePrevSibling : function(rc)
    {
        var siblings,
            parent,
            index,
            result = null;
                
        parent = this.getNodeParent(rc);
        if (parent) {
            siblings = this.getNodeChildren(parent);
        }
        else {
            siblings = this.getRootNodes();
        }
        
        index = siblings.indexOf(rc);
        if (index > 0) {
            result = siblings[index - 1];
        }
        
        return result;
    },
    
    // Abstract tree support methods.
    // -----------------------------------------------------------------------------------------------
    
    // Public - Abstract
    getRootNodes : function()
    {
        throw 'Abstract method call';
    },
    
    // Public - Abstract
    getNodeDepth : function(rc)
    {
        throw 'Abstract method call';
    },
    
    // Public - Abstract
    getNodeParent : function(rc)
    {
        throw 'Abstract method call';
    },
    
    // Public - Abstract
    getNodeChildren : function(rc)
    {
        throw 'Abstract method call';
    },
    
    // Public - Abstract
    addToNode : function(parent, child)
    {
        throw 'Abstract method call';
    },
    
    // Public - Abstract
    removeFromNode : function(parent, child)
    {
        throw 'Abstract method call';
    },
    
    // Paging support methods.
    // -----------------------------------------------------------------------------------------------
    
    getPageOffset : function()
    {
        return this.page_offset;
    },
    
    
    getActiveNodePageOffset : function()
    {
        var result;
        
        if (this.active_node) {
            result = this.getNodeChildrenOffset(this.active_node);
        }
        else {
            result = this.getPageOffset();
        }
        
        return result;
    },
    
    
    getActiveNodeCount : function()
    {
        var result;
        
        if (this.active_node) {
            result = this.getNodeChildrenCount(this.active_node);
        }
        else {
            result = this.getRootNodes().length;
        }
        
        return result;
    },
    
    
    getActiveNodeTotalCount : function()
    {
        var result;
        
        if (this.active_node) {
            result = this.getNodeChildrenTotalCount(this.active_node);
        }
        else {
            result = this.getTotalCount();
        }
        
        return result;  
    },

    
    isTaskChildOf : function(child, parent)
    {
        while (child && (child = this.getNodeParent(child))) {
            if (child === parent) {
                return true;
            }
        }

        return false;
    }
});


Ext.ns('Ext.ux.maximgb.tg');


Ext.ux.maximgb.tg.AdjacencyListStore = Ext.extend(Ext.ux.maximgb.tg.AbstractTreeStore,
{
    
    parent_id_field_name : 'ParentId',
    
    getRootNodes : function()
    {
        var pField = this.parent_id_field_name;
        return this.queryBy(function(r) { 
            return r.data[pField] === null || r.data[pField] === ""; 
        }).items;
    },
    
    getNodeDepth : function(rc)
    {
        return this.getNodeAncestors(rc).length;
    },
    
    getNodeParent : function(rc)
    {
        return this.getById(rc.get(this.parent_id_field_name));
    },
    
    // Modified, inlined version of "query" method for speed
    getNodeChildren : function(rc)
    {   
        var rs = [], 
            records = this.data.items,
            id = rc.id,
            pField = this.parent_id_field_name,
            r;
            
        for(var i = 0, len = records.length; i < len; i++){
            if(records[i].data[pField] === id){
                rs.push(records[i]);
            }
        }
        return rs;
    },
    
    addToNode : function(parent, child)
    {
        child.set(this.parent_id_field_name, parent.id);
        this.addSorted(child);
    },
    
    removeFromNode : function(parent, child)
    {
        this.remove(child);
    },
    
    hasChildNodes : function(rc)
    {
        return this.findExact(this.parent_id_field_name, rc.id) > 0;
    }
});

Ext.reg('Ext.ux.maximgb.tg.AdjacencyListStore', Ext.ux.maximgb.tg.AdjacencyListStore);



Ext.ux.maximgb.tg.NestedSetStore = Ext.extend(Ext.ux.maximgb.tg.AbstractTreeStore,
{
    
    left_field_name : '_lft',
    
    
    right_field_name : '_rgt',
    
    
    level_field_name : '_level',
    
    
    root_node_level : 1,
    
    getRootNodes : function()
    {
        var i, 
            len, 
            result = [], 
            records = this.data.getRange();
        
        for (i = 0, len = records.length; i < len; i++) {
            if (records[i].get(this.level_field_name) == this.root_node_level) {
                result.push(records[i]);
            }
        }
        
        return result;
    },
    
    getNodeDepth : function(rc)
    {
        return rc.get(this.level_field_name) - this.root_node_level;
    },
    
    getNodeParent : function(rc)
    {
        var result = null,
            rec, records = this.data.getRange(),
            i, len,
            lft, r_lft,
            rgt, r_rgt,
            level, r_level;
                
        lft = rc.get(this.left_field_name);
        rgt = rc.get(this.right_field_name);
        level = rc.get(this.level_field_name);
        
        for (i = 0, len = records.length; i < len; i++) {
            rec = records[i];
            r_lft = rec.get(this.left_field_name);
            r_rgt = rec.get(this.right_field_name);
            r_level = rec.get(this.level_field_name);
            
            if (
                r_level == level - 1 &&
                r_lft < lft &&
                r_rgt > rgt
            ) {
                result = rec;
                break;
            }
        }
        
        return result;
    },
    
    getNodeChildren : function(rc)
    {
        var lft, r_lft,
            rgt, r_rgt,
            level, r_level,
            records, rec,
            result = [];
                
        records = this.data.getRange();
        
        lft = rc.get(this.left_field_name);
        rgt = rc.get(this.right_field_name);
        level = rc.get(this.level_field_name);
        
        for (i = 0, len = records.length; i < len; i++) {
            rec = records[i];
            r_lft = rec.get(this.left_field_name);
            r_rgt = rec.get(this.right_field_name);
            r_level = rec.get(this.level_field_name);
            
            if (
                r_level == level + 1 &&
                r_lft > lft &&
                r_rgt < rgt
            ) {
                result.push(rec);
            }
        }
        
        return result;
    }
});


Ext.ns('Sch.gantt.plugins');


Sch.gantt.plugins.TaskContextMenu = Ext.extend(Ext.menu.Menu, {
    
    plain : true,
    
    
    triggerEvent : 'taskcontextmenu',

    texts : {
        newTaskText : 'New task', 
        newMilestoneText : 'New milestone', 
        deleteTask : 'Delete task',
        editLeftLabel : 'Edit left label',
        editRightLabel : 'Edit right label',
        add : 'Add...',
        deleteDependency : 'Delete dependency...',
        addTaskAbove : 'Task above',
        addTaskBelow : 'Task below',
        addMilestone : 'Milestone',
        addSubtask :'Sub-task',
        addSuccessor : 'Successor',
        addPredecessor : 'Predecessor'
    },
    
    buildMenuItems : function() {
        var t = this.texts,
            actions = this.actions;

        Ext.apply(this, {
            items : [
                {
                    handler : actions.deleteTask,
                    scope : this,
                    text : t.deleteTask
                },
                {
                    handler : actions.editLeftLabel,
                    scope : this,
                    text : t.editLeftLabel
                },
                {
                    handler : actions.editRightLabel,
                    scope : this,
                    text : t.editRightLabel
                },
                {
                    scope : this,
                    text : t.add,
                    menu : {
                        plain : true,
                        items : [
                            {
                                handler : actions.addTaskAbove,
                                scope : this,
                                text : t.addTaskAbove
                            },
                            {
                                handler : actions.addTaskBelow,
                                scope : this,
                                text : t.addTaskBelow
                            },
                            {
                                handler : actions.addMilestone,
                                scope : this,
                                text : t.addMilestone
                            },
                            {
                                handler : actions.addSubtask,
                                scope : this,
                                text : t.addSubtask
                            },
                            {
                                handler : actions.addSuccessor,
                                scope : this,
                                text : t.addSuccessor
                            },
                            {
                                handler : actions.addPredecessor,
                                scope : this,
                                text : t.addPredecessor
                            }
                        ]
                    }
                },
                {
                    text : t.deleteDependency,
                    menu : { 
                        plain : true,
                        listeners : {
                            beforeshow : {
                                fn : this.populateDependencyMenu,
                                scope : this
                            },
                    
                            // highlight dependencies on mouseover of the menu item
                            mouseover : {
                                fn : this.onDependencyMouseOver,
                                scope : this
                            },
                    
                            // unhighlight dependencies on mouseout of the menu item
                            mouseout : {
                                fn : this.onDependencyMouseOut,
                                scope : this
                            }
                        }
                    }
                }
            ]
        });
    },

    initComponent : function() {
        this.buildMenuItems();
        
        Sch.gantt.plugins.TaskContextMenu.superclass.initComponent.call(this);
    },
    
    populateDependencyMenu : function(menu) {
        var g = this.grid, 
            taskStore = g.getTaskStore(),
            dependencies = g.getDependenciesForTask(this.rec),
            depStore = g.dependencyStore;
        
        menu.removeAll();

        if (dependencies.length === 0) {
            return false;
        }
        
        dependencies.each(function(dep) {
            var fromId = dep.get('From'),
                task = g.getTaskById(fromId == this.rec.id ? dep.get('To') : fromId),
                text = Ext.util.Format.ellipsis(task.get('Name'), 30);
            
            menu.addMenuItem({
                depId : dep.id,
                text : text,
                scope : this,
                handler : function(menuItem) {
                    depStore.removeAt(depStore.indexOfId(menuItem.depId));
                }
            });
        }, this);
    },
    
    onDependencyMouseOver : function(menu, e, item) {
        if (item) {
            this.grid.highlightDependency(item.depId);
        }
    },
    
    onDependencyMouseOut : function(menu, e, item) {
        if (item) {
            this.grid.unhighlightDependency(item.depId);
        }
    },
        
    cleanUp : function() {
        if (this.menu) {
            this.menu.destroy();
        }
    },
    
    init:function(grid) {
        grid.on('destroy', this.cleanUp, this);
        grid.on(this.triggerEvent, this.onActivate, this);
        this.grid = grid;
    },
    
    onActivate : function(g, rec, e) {
        e.stopEvent();

        if (typeof rec === 'number') {
            rec = this.grid.getTaskStore().getAt(rec);
        }

        this.rec = rec;
        this.showAt(e.getXY());
    },
    
    copyTask : function(originalRecord) {
        var s = originalRecord.store,
            newTask = new s.recordType({
                PercentDone : 0,
                Name : this.texts.newTaskText,
                StartDate : originalRecord.get('StartDate'),
                EndDate : originalRecord.get('EndDate'),
                ParentId : originalRecord.get('ParentId'),
                IsLeaf : true
            });
        return newTask;
    },
    
    actions : {
        deleteTask: function() {
            this.grid.getTaskStore().remove(this.rec);
        },
        
        editLeftLabel : function() {
            this.grid.editLeftLabel(this.rec);
        },
            
        editRightLabel : function() {
            this.grid.editRightLabel(this.rec);
        },
            
        addTaskAbove : function() {
            var s = this.rec.store,
                newTask = this.copyTask(this.rec);
                
            s.insert(s.indexOf(this.rec), newTask);
        },
            
        addTaskBelow : function() {
            var s = this.rec.store,
                newTask = this.copyTask(this.rec), 
                insertIndex;
            
            if (s.isLeafNode(this.rec)) {
                insertIndex = s.indexOf(this.rec) + 1;
            } else {
                var sibling = s.getNodeNextSibling(this.rec);
                
                insertIndex = sibling ? s.indexOf(sibling) : s.getCount();
            }
            s.insert(insertIndex, newTask);
        },
            
        addSubtask : function() {
            var s = this.rec.store,
                newTask = this.copyTask(this.rec);
            
            this.rec.set(this.rec.store.leaf_field_name, false);
            s.addToNode(this.rec, newTask);
            s.expandNode(this.rec);
        },
            
        addSuccessor : function() {
            var s = this.rec.store,
                depStore = this.grid.dependencyStore,
                index = this.rec.store.indexOf(this.rec),
                newTask = this.copyTask(this.rec);
            
            newTask.set('StartDate', this.rec.get('EndDate'));
            newTask.set('EndDate', Sch.util.Date.add(this.rec.get('EndDate'), Sch.util.Date.DAY, 1));
            
            s.insert(index + 1, newTask);
            depStore.add(new depStore.recordType({
                    From : this.rec.id,
                    To : newTask.id,
                    Type : Sch.gantt.Dependency.EndToStart
                })
            );
        },
            
        addPredecessor : function() {
           var s = this.rec.store,
                depStore = this.grid.dependencyStore,
                index = this.rec.store.indexOf(this.rec),
                newTask = this.copyTask(this.rec),
                newEnd = this.rec.get('StartDate');
            
            newTask.set('EndDate', newEnd);
            newTask.set('StartDate', Sch.util.Date.add(newEnd, Sch.util.Date.DAY, -1));
            
            s.insert(index, newTask);
            depStore.add(new depStore.recordType({
                    From : newTask.id,
                    To : this.rec.id,
                    Type : Sch.gantt.Dependency.EndToStart
                })
            );
        },
            
        addMilestone : function() {
            var s = this.rec.store,
                newMilestone = this.copyTask(this.rec);
                index = this.rec.store.indexOf(this.rec);
            newMilestone.set('StartDate', newMilestone.get('EndDate'));    
            s.insert(index + 1, newMilestone);
       }
    }
}); 


Ext.ns('Sch.gantt.plugins');


Sch.gantt.LabelEditor = function(gantt, config) {
    this.gantt = gantt; 
    gantt.on('afterrender', this.onGanttRender, this);
    Ext.apply(this, config);
    Sch.gantt.LabelEditor.superclass.constructor.call(this, config);
};
 
Ext.extend(Sch.gantt.LabelEditor, Ext.Editor, {
    
    // Programmatically enter edit mode
    edit : function(record) {
        var wrap = this.gantt.view.getElementFromEventRecord(record).up(this.gantt.eventWrapSelector);
        this.startEdit(wrap.child(this.delegate));
        this.record = record;
    },
    
    // private, must be supplied
    delegate : '',
    
    // private, must be supplied
    dataIndex : '',

    shadow: false,
    completeOnEnter: true,
    cancelOnEsc: true,
    ignoreNoChange: true,
    
    onGanttRender : function(gantt) {
        if (!this.field.width) {
            this.autoSize = 'width';
        }

        this.on({
            beforestartedit : function(editor, el, value) {
                return gantt.fireEvent('labeledit_beforestartedit', gantt, this.record, value);
            },
            beforecomplete : function(editor, value, original) {
                return gantt.fireEvent('labeledit_beforecomplete', gantt, value, original, this.record);
            },
            complete : function(editor, value, original) {
                this.record.set(this.dataIndex, value);
                gantt.fireEvent('labeledit_complete', gantt, value, original, this.record);
            },
            scope : this
        });
        
        gantt.getView().mainBody.on('dblclick', function(e, t){
            this.record = gantt.resolveTask(t);
            this.startEdit(t);
            this.setValue(this.record.get(this.dataIndex));
        }, this, {
            delegate: this.delegate
        });
    }
}); 



Ext.ns('Sch.gantt');

Sch.gantt.MilestoneTemplate = function(cfg) {
    return new Ext.XTemplate(
        '<div class="sch-event-wrap sch-gantt-milestone {ctcls} x-unselectable" style="left:{leftOffset}px">'+
            // Left label 
            '<div class="sch-gantt-labelct-left"><label class="sch-gantt-label sch-gantt-label-left">{leftLabel}</label></div>'+
                    
            // Milestone indicator
            '<div id="{id}" class="sch-gantt-item sch-gantt-milestone-diamond {internalcls} {cls}" unselectable="on" style="{style}">'+
                // Dependency terminals
                (cfg.enableDependencyDragDrop ? '<div class="sch-gantt-terminal sch-gantt-terminal-start"></div><div class="sch-gantt-terminal sch-gantt-terminal-end"></div>' : '') +
            '</div>'+
                    
            // Right label 
            '<div class="sch-gantt-labelct-right"><label class="sch-gantt-label sch-gantt-label-right">{rightLabel}</label></div>'+
        '</div>' +

        // Baseline Task bar
        (cfg.showBaseline ? '<div id="{id}-base" class="sch-gantt-task-diamond-baseline {basecls}" style="left:{[values.baseLeftOffset - 8]}px;"></div>' : ''),
    {
        compiled: true,      
        disableFormats: true 
    });
};


Ext.ns('Sch.gantt');

Sch.gantt.ParentTaskTemplate = function(cfg) {
    return new Ext.XTemplate(
        '<div class="sch-event-wrap sch-gantt-parent-task {ctcls}" style="left:{leftOffset}px;width:{[values.width + 12]}px">'+
            // Left label 
            '<div class="sch-gantt-labelct-left"><label class="sch-gantt-label sch-gantt-label-left">{leftLabel}</label></div>'+
                    
            // Task bar
            '<div id="{id}" class="sch-gantt-item sch-gantt-parenttask-bar {internalcls} {cls}" style="{style}">'+
                // Left terminal
                (cfg.enableDependencyDragDrop ? '<div class="sch-gantt-terminal sch-gantt-terminal-start"></div>' : '') +
                        
                '<div class="sch-gantt-progress-bar" style="width:{percentDone}%">&#160;</div>'+
                        
                '<div class="sch-gantt-parenttask-arrow sch-gantt-parenttask-leftarrow"></div>'+
                '<div class="sch-gantt-parenttask-arrow sch-gantt-parenttask-rightarrow"></div>'+
                // Right terminal
                (cfg.enableDependencyDragDrop ? '<div class="sch-gantt-terminal sch-gantt-terminal-end"></div>' : '') +
            '</div>'+
                    
            // Right label 
            '<div class="sch-gantt-labelct-right" style="left:{width}px"><label class="sch-gantt-label sch-gantt-label-right">{rightLabel}</label></div>' +
        '</div>' +

        // Baseline Task bar
        (cfg.showBaseline ? '<div id="{id}-base" class="sch-gantt-task-bar-baseline {basecls}" style="left:{baseLeftOffset}px;width:{baseWidth}px"></div>' : ''),
    {
        compiled: true,      
        disableFormats: true 
    });
};


Ext.ns('Sch.plugins');


Sch.gantt.plugins.DependencyEditor = Ext.extend(Ext.form.FormPanel, {
    
    
    hoursText : 'hrs',
    
    
    hideOnBlur : true,
    
    
    fromText : 'From',
    
    
    toText : 'To',

    
    typeText : 'Type',

    
    lagText : 'Lag',

    
    endToStartText : 'Finish-To-Start',

    
    startToStartText : 'Start-To-Start',

    
    endToEndText : 'Finish-To-Finish',

    
    startToEndText : 'Start-To-Finish',
       
    
    showLag : false,

    
    show : function(dependencyRecord, xy) {
        this.dependencyRecord = dependencyRecord;
        
        // Load form panel fields
        this.getForm().loadRecord(dependencyRecord);
        this.fromLabel.setValue(this.taskStore.getById(this.dependencyRecord.get('From')).get('Name'));
        this.toLabel.setValue(this.taskStore.getById(this.dependencyRecord.get('To')).get('Name'));

        this.el.setXY(xy);
        this.expand();
    },
    
    border : false,
    height: 130,
    width : 260,
    frame : true,
    labelWidth : 60,
    header : false,

    initComponent : function() {
        Ext.apply(this, {
            items : this.buildFields()
        });
        Sch.gantt.plugins.DependencyEditor.superclass.initComponent.call(this);
    },

    buildFields : function() {
        var me = this,
            sd = Sch.gantt.Dependency,
            fs = [
            this.fromLabel = new Ext.form.DisplayField({
                fieldLabel : this.fromText
            }),
                    
            this.toLabel = new Ext.form.DisplayField({
                fieldLabel : this.toText
            }),

            this.typeField = new Ext.form.ComboBox({
                name : 'Type',
                fieldLabel : this.typeText,
                triggerAction : 'all',
                mode : 'local',
                valueField : 'value',
                displayField : 'text',
                editable : false,
                width : 170,
                store : new Ext.data.JsonStore({
                    fields : ['text', 'value'],
                    data : [{
                        text : this.endToStartText,
                        value : sd.EndToStart
                    },
                    {
                        text : this.startToStartText,
                        value : sd.StartToStart
                    },
                    {
                        text : this.endToEndText,
                        value : sd.EndToEnd
                    },
                    {
                        text : this.startToEndText,
                        value : sd.StartToEnd
                    }]
                })
            })
        ];

        if (this.showLag) {
            fs.push(
                this.lagField = new Ext.ux.form.SpinnerField({
                    width : 50,
                    name : 'Lag',
                    fieldLabel : this.lagText
                })
            );
        }

        return fs;
    },
    
    init : function(cmp) {
        cmp.on('dependencydblclick', this.onDependencyDblClick, this);
        cmp.on('render', this.onGanttRender, this);
        
        this.gantt = cmp;
        this.taskStore = cmp.getTaskStore();
    },
    
    onDependencyDblClick : function(g, record, e, t) {
        this.show(record, e.getXY());
    },
    
    onGanttRender : function() {
        this.render(Ext.getBody());

        this.el.addClass('sch-gantt-dependencyeditor');

        // Collapse after render, otherwise rendering is messed up
        this.collapse();
         
        if (this.hideOnBlur) {
            // Hide when clicking outside panel
            this.mon(Ext.getBody(), 'click', this.onMouseClick, this);
        }
    },

    onMouseClick : function(e){
        var tf = this.typeField;
        
        if (!this.collapsed && 
            !e.within(this.getEl()) &&
            (!tf.view || !e.within(tf.view.el))) {
            this.collapse(false);
        }
    }
});



Ext.ns('Sch.gantt');

Sch.gantt.TaskTemplate = function(cfg) {
    return new Ext.Template(
        '<div class="sch-event-wrap sch-gantt-task {ctcls} x-unselectable" style="left:{leftOffset}px;">' +
            // Left label 
            '<div class="sch-gantt-labelct-left"><label class="sch-gantt-label sch-gantt-label-left">{leftLabel}</label></div>'+
                    
            // Task bar
            '<div id="{id}" class="sch-gantt-item sch-gantt-task-bar {internalcls} {cls}" unselectable="on" style="width:{width}px;{style}">'+
                // Left terminal
                (cfg.enableDependencyDragDrop ? '<div unselectable="on" class="sch-gantt-terminal sch-gantt-terminal-start"></div>' : '') +
                ((cfg.resizeHandles === 'both' || cfg.resizeHandles === 'left') ? '<div class="x-resizable-handle x-resizable-handle-west sch-gantt-task-handle"></div>' : '') +
                
                '<div class="sch-gantt-progress-bar" style="width:{percentDone}%" unselectable="on">&#160;</div>' +

                ((cfg.resizeHandles === 'both' || cfg.enableResizeHandles === 'right') ? '<div class="x-resizable-handle x-resizable-handle-east"></div>' : '') +
                // Right terminal
                (cfg.enableDependencyDragDrop ? '<div unselectable="on" class="sch-gantt-terminal sch-gantt-terminal-end"></div>' : '') +
                (cfg.enableProgressBarResize ? '<div style="left:{percentDone}%" class="sch-gantt-progressbar-handle"></div>': '') +
            '</div>' +
                   
            // Right label 
            '<div class="sch-gantt-labelct-right" style="left:{width}px"><label class="sch-gantt-label sch-gantt-label-right">{rightLabel}</label></div>'+
        '</div>' +
        // Baseline Task bar
        (cfg.showBaseline ? '<div id="{id}-base" class="sch-gantt-task-bar-baseline {basecls}" style="left:{baseLeftOffset}px;width:{baseWidth}px"></div>' : ''),
    {
        compiled: true,      
        disableFormats: true 
    });
};


Ext.ns('Sch.gantt');


Sch.gantt.DragDrop = function(gantt, config) {
    Ext.apply(this, config);
    
    // private
    this.gantt = gantt;
    this.ddGroup = gantt.id;

    gantt.on({
        afterrender : this.onRender, 
        beforedestroy : this.cleanUp,
        scope : this
    });
        
    Sch.gantt.DragDrop.superclass.constructor.call(this);
};

Ext.extend(Sch.gantt.DragDrop, Ext.util.Observable, {
    
    useTooltip : true,
    
    
    validatorFn : function(record, date, duration, e) {
        return true;
    },
    
    
    validatorFnScope : null,
    
    cleanUp : function() {
        this.gantt.dragZone.destroy();

        if (this.tip) {
            this.tip.destroy();
        }
    },
    
    // private
    onRender : function() {
        this.setupDragZone();
    },
    
    setupDragZone : function() {
        var me = this,
            g = this.gantt,
            v = g.getView();
            
        if (this.useTooltip) {
            this.tip = new Sch.gantt.Tooltip({}, g);
        }
        
        // The drag zone behaviour
        g.dragZone = new Sch.gantt.DragZone(v.mainBody, {
            ddGroup : this.ddGroup,
            validatorFn : this.validatorFn,
            validatorFnScope : this.validatorFnScope,
            gantt : g,
            tip : this.tip
        });
    }
});



Sch.DragProxy = Ext.extend(Ext.dd.StatusProxy, {
    constructor : function(config){
        Ext.apply(this, config);
        this.id = this.id || Ext.id();
        this.el = new Ext.Layer({
            dh: {
                id: this.id, tag: "div", cls: "sch-gantt-dragproxy x-dd-drag-proxy", children: [
                    {tag: "div", cls: "x-dd-drag-ghost"}
                ]
            }, 
            shadow: false
        });
        this.ghost = Ext.get(this.el.dom.childNodes[0]);
        this.dropStatus = this.dropNotAllowed;
    },
    
    // Overridden to prevent the proxy from resetting itself and resetting the class property
    reset : function(clearGhost){
        this.el.dom.className = "sch-gantt-dragproxy x-dd-drag-proxy " + this.dropNotAllowed;
        this.dropStatus = this.dropNotAllowed;
        if(clearGhost){
            this.ghost.update("");
        }
    }
});




Sch.gantt.DragZone = Ext.extend(Ext.dd.DragZone, {
    containerScroll : false,
    getItemDepth : 8,
    
    constructor : function(el, config){
        config.proxy = new Sch.DragProxy({
            shadow : false,
            dropAllowed : Ext.dd.StatusProxy.prototype.dropAllowed,
            dropNotAllowed : Ext.dd.StatusProxy.prototype.dropNotAllowed
        });
        Sch.gantt.DragZone.superclass.constructor.apply(this, arguments);
        this.scroll = false;
        this.isTarget = true;
        this.ignoreSelf = false;

        // Stop task drag and drop when a resize handle, a terminal or a parent task is clicked
        this.addInvalidHandleClass('x-resizable-handle');
        this.addInvalidHandleClass('sch-gantt-terminal');
        this.addInvalidHandleClass('sch-gantt-progressbar-handle');

        Ext.dd.ScrollManager.register(this.gantt.getView().scroller);
    },

    destroy : function(){
        Sch.gantt.DragZone.superclass.destroy.call(this);
        Ext.dd.ScrollManager.unregister(this.gantt.getView().scroller);
    },
    
    autoOffset: function(x, y) {
        var xy = this.dragData.repairXY, // Original position of the element
            xDelta = x - xy[0],
            yDelta = y - xy[1];
        
        this.setDelta(xDelta, yDelta);
    },
    
    constrainTo : function(constrainTo){
        var ce = Ext.get(constrainTo),
            xy = ce.getXY();

        this.resetConstraints();
        this.initPageY = xy[1];

        this.setYConstraint(0, 0, this.yTickSize);
    },

    onDragOver: function(e, id){
                
        var g = this.gantt,
            x = this.proxy.el.getX() + g.getXOffset(this.dragData.record), // Adjust x position for certain task types
            newStart = g.getDateFromX(x, 'floor'),
            data = this.dragData;
               
        if (!data.hidden) {
            Ext.fly(data.sourceNode).hide();
            data.hidden = true;
        }
                
        if (!newStart) return;
                
        data.start = newStart;
        this.valid = this.validatorFn.call(this.validatorFnScope || g, 
                                            data.record, 
                                            newStart, 
                                            data.duration, 
                                            e) !== false;
        if (this.tip) {
            var end = Sch.util.Date.add(newStart, Sch.util.Date.MINUTE, data.duration);
            this.tip.update(newStart, end, this.valid);
        }
    },
            
    onStartDrag : function () {
        var rec = this.dragData.record;

        if (this.tip) {
            var start = rec.get('StartDate'),
                end = rec.get('EndDate');
            this.tip.show(Ext.get(this.dragData.sourceNode));
            this.tip.update(start, end, true);
        }
               
                
        this.gantt.fireEvent('dndstart', this.gantt, rec);
    },
            
    // On receipt of a mousedown event, see if it is within a draggable element.
    // Return a drag data object if so. The data object can contain arbitrary application
    // data, but it should also contain a DOM element in the ddel property to provide
    // a proxy to drag.
    getDragData: function(e) {
        var g = this.gantt,
            sourceNode = e.getTarget(g.eventSelector, this.getItemDepth);
                
        if (sourceNode) {
            var sourceNodeEl = Ext.get(sourceNode),
                v = g.getView(),
                taskEl = sourceNodeEl.is(g.eventSelector) ? sourceNode : sourceNodeEl.up(g.eventSelector).dom,
                sourceTaskRecord = g.resolveTask(taskEl.id);
                     
            if (g.fireEvent('beforednd', g, sourceTaskRecord, e) === false) {
                return null;
            }
                    
            var copy = sourceNode.cloneNode(true),
                increment = v.getSnapPixelAmount(),
                origXY = Ext.fly(sourceNode).getXY();
            copy.id = Ext.id();
            
            if (increment <= 1) {
                Ext.fly(copy).setStyle('left', 0);  // Reset any offset applied through CSS
            }
            
            this.setXConstraint(0, this.el.getWidth(), increment > 1 ? increment : 0);
            this.constrainTo(sourceNodeEl.up('div.x-grid3-row'));

            return {
                sourceNode : sourceNode,
                repairXY: origXY,
                ddel: copy,
                record : sourceTaskRecord,
                duration : Sch.util.Date.getDurationInMinutes(sourceTaskRecord.get('StartDate'), sourceTaskRecord.get('EndDate'))
            };
        }
        return null;
    },
            
    // Override, get rid of weird highlight fx in default implementation
    afterRepair : function(){
        Ext.fly(this.dragData.sourceNode).show();
        if (this.tip) {
            this.tip.hide();
        }
        this.dragging = false;
    },

    // Provide coordinates for the proxy to slide back to on failed drag.
    // This is the original XY coordinates of the draggable element.
    getRepairXY: function() {
        this.gantt.fireEvent('afterdnd', this.gantt);
        return this.dragData.repairXY;
    },
            
    onDragDrop: function(e, id){
        var target = this.cachedTarget || Ext.dd.DragDropMgr.getDDById(id),
            data = this.dragData,
            g = this.gantt,
            start = data.start,
            modified = false;
                
        if (start) {
            var end = Sch.util.Date.add(start, Date.MINUTE, data.duration),
                valid = false;
                    
            if (start && this.valid) {
                var r = data.record;

                modified = (r.get('StartDate') - start) !== 0 || (r.get('EndDate') - end) !== 0;

                r.beginEdit();
                r.set('StartDate', start);
                r.set('EndDate', end);
                r.endEdit();
                valid = true;
                        
                g.fireEvent('drop', g, r);
            }
        }
                
        if (this.tip) {
            this.tip.hide();
        }
                    
        g.fireEvent('afterdnd', g);

        if(valid && modified){ // valid drop?
            this.onValidDrop(target, e, id);
        }else{
            this.onInvalidDrop(target, e, id);
        }
    }
});




Ext.ns('Sch.gantt');
 

Sch.gantt.Resize = function(gantt, config) {
    Ext.apply(this, config);
    
    this.scheduler = gantt;
        
    this.scheduler.on({
        render : this.onSchedulerRender, 
        destroy : this.cleanUp,
        scope : this
    });

    Sch.gantt.Resize.superclass.constructor.call(this);
};
 
Ext.extend(Sch.gantt.Resize, Ext.util.Observable, {
    
    
    showDuration : true,
    
    
    useTooltip : true,
    
    
    validatorFn : Ext.emptyFn,
    
    
    validatorFnScope : null,
    
    // private
    onSchedulerRender : function() {
        var s = this.scheduler;

        s.mon(s.getView().mainBody, 'mousedown', this.onMouseDown, this, { delegate : '.x-resizable-handle' });
    },
    
    onMouseDown : function(e) {
        var s = this.scheduler,
            domEl = e.getTarget(s.eventSelector),
            rec = s.resolveTask(domEl);

        if (s.fireEvent('beforeresize', s, rec, e) === false) {
            return;
        }
        e.stopEvent();
        this.createResizable(Ext.get(domEl), rec, e);
        s.fireEvent('resizestart', s, rec);
    },


    // private
    createResizable : function (el, eventRecord, e) {
       
        var t = e.getTarget(),
            s = this.scheduler,
            v = s.getView(),
            cm = s.getColumnModel(),
            isStart = !!t.className.match('x-resizable-handle-west'),
            widthIncrement = v.getSnapPixelAmount(),
            startLeft = el.getLeft(),
            startRight = el.getRight(),
            currentWidth = el.getWidth(),
            resourceRegion = el.up('.x-grid3-row').getRegion();

        this.resizable = new Sch.LazyResizable(el, {
                startLeft : startLeft,
                startRight : startRight,
                eventRecord : eventRecord,
                handles: isStart ? 'w' : 'e',
                dynamic: true,
                handleCls : 'x-hidden',
                maxWidth : currentWidth + (isStart ? (startLeft - resourceRegion.left) : (resourceRegion.right - startRight)),
                minWidth: widthIncrement,
                widthIncrement : widthIncrement,
                listeners : {
                    partialresize : { fn : this[isStart ? 'partialWestResize' : 'partialEastResize'], scope : this },
                    resize        : { fn : this.afterResize, scope : this }
                }
            },
            isStart ? 'west' : 'east',
            e
        );
        
        if (this.useTooltip) {
            if(!this.tip) {
                this.tip = new Sch.gantt.Tooltip({
                    mode : this.showDuration ? 'duration' : 'startend'
                }, s);
            }
            var start = eventRecord.get('StartDate'),
                end = eventRecord.get('EndDate');
            this.tip.show(el);
            this.tip.update(start, end, true);
        }
    },
    
    // private
    partialEastResize : function (r, newWidth, oldWidth, e) {
        var s = this.scheduler,
            end = s.getDateFromX(r.startLeft + Math.min(newWidth, this.resizable.maxWidth), 'round');
        
        if (!end) {
            return;
        }

        var start = r.eventRecord.get('StartDate'),
            valid = this.validatorFn.call(this.validatorFnScope || this, r.eventRecord, start, end) !== false;
        
        r.end = end;
        
        s.fireEvent('partialresize', s, r.eventRecord, start, end, r.el, e);
        
        if (this.useTooltip) {
            this.tip.update(start, end, valid);
        }
    },
    
    partialWestResize : function (r, newWidth, oldWidth, e) {
        var s = this.scheduler,
            start = s.getDateFromX(r.startRight - newWidth, 'round');
        
        if (!start) {
            return;
        }

        var end = r.eventRecord.get('EndDate'),
            valid = this.validatorFn.call(this.validatorFnScope || this, r.eventRecord, start, end) !== false;
        
        r.start = start;
        
        s.fireEvent('partialresize', s, r.eventRecord, start, end, r.el, e);
        
        if (this.useTooltip) {
            this.tip.update(start, end, valid);
        }
    },
    
    // private
    afterResize : function (r, w, h, e) {
        if (this.useTooltip) {
            this.tip.hide();
        }
        var eventRecord = r.eventRecord,
            oldStart = eventRecord.get('StartDate'),
            oldEnd = eventRecord.get('EndDate'),
            start = r.start || oldStart,
            end = r.end || oldEnd,
            scheduler = this.scheduler;
        
        if (start && end && (end - start > 0) && // Input sanity check
            ((start - oldStart !== 0) || (end - oldEnd !== 0)) && // Make sure start OR end changed
            this.validatorFn.call(this.validatorFnScope || this, eventRecord, start, end, e) !== false) {
            
            eventRecord.beginEdit();
            eventRecord.set('StartDate', start);
            eventRecord.set('EndDate', end);
            eventRecord.endEdit();
        } else {
            scheduler.getView().refreshRow(eventRecord);
        }
        
        // Destroy resizable 
        r.destroy();
        
        scheduler.fireEvent('afterresize', scheduler, eventRecord);
    },

    cleanUp : function() {
        if (this.tip) {
            this.tip.destroy();
        }
    }
}); 


 
Ext.ns('Sch.gantt');


Sch.gantt.RowSelectionModel = Ext.extend(Ext.grid.RowSelectionModel, {
    
    initEvents : function() {
        if(!this.grid.enableDragDrop && !this.grid.enableDrag){
            this.grid.on('rowmousedown', this.handleMouseDown, this);
        }
        
        this.rowNav = new Ext.KeyNav(this.grid.getGridEl(), {
            up: this.onKeyPress, 
            down: this.onKeyPress,
            left: this.onLeftRightKeyPress, 
            right: this.onLeftRightKeyPress,
            scope: this
        });

        this.grid.getView().on({
            scope: this,
            refresh: this.onRefresh,
            rowupdated: this.onRowUpdated,
            rowremoved: this.onRemove
        });
    },

    handleMouseDown : Ext.grid.RowSelectionModel.prototype.handleMouseDown.createInterceptor(function(g, rowIndex, e){
        return !!e.getTarget('.x-grid3-locked');
    }),

    onKeyPress : function(e, name){
        var up = name == 'up',
            method = up ? 'selectPrevious' : 'selectNext',
            last;
        if(!e.shiftKey || this.singleSelect){
            this[method](false);
        }else if(this.last !== false && this.lastActive !== false){
            last = this.last;
            var newIndex = up ? this.findPreviousVisibleIndex(this.lastActive - 1) : this.findNextVisibleIndex(this.lastActive + 1);
            if (newIndex >= 0 ) {
                this.selectRange(this.last, newIndex);
                this.grid.getView().focusRow(this.lastActive);
            }
            if(last !== false){
                this.last = last;
            }
        }else{
           this.selectFirstRow();
        }
    },

    onLeftRightKeyPress : function(e, name){
        var taskStore = this.grid.getTaskStore(),
            left = name === 'left';

        if(!e.shiftKey && this.lastActive !== false){
            var t = taskStore.getAt(this.lastActive);
            
            if (!taskStore.isLeafNode(t)) {
                taskStore[left ? 'collapseNode' : 'expandNode'](t);
            }
        }
    },

    findPreviousVisibleIndex : function(index) {
        var store = this.grid.getTaskStore();

        while(index >= 0) {
            if (store.isVisibleNode(store.getAt(index))) {
                return index;
            }
            index--;
        }
        return -1;
    },

    findNextVisibleIndex : function(index) {
        var store = this.grid.getTaskStore(),
            count = store.getCount();
           
        while(index < count) {
            if (store.isVisibleNode(store.getAt(index))) {
                return index;
            }
            index++;
        }
        return -1;
    },

    selectNext : function(keepExisting){
        if(this.hasNext()){
            this.selectRow(this.findNextVisibleIndex(this.last + 1), keepExisting);
            this.grid.getView().focusRow(this.last);
            return true;
        }
        return false;
    },
    
    selectPrevious : function(keepExisting){
        if(this.hasPrevious()){
            this.selectRow(this.findPreviousVisibleIndex(this.last - 1), keepExisting);
            this.grid.getView().focusRow(this.last);
            return true;
        }
        return false;
    },

    acceptsNav : function(row, col, cm){
        return this.grid.isCellEditable(col, row) && Sch.gantt.RowSelectionModel.superclass.acceptsNav.apply(this, arguments);
    }
});


Ext.ns('Sch.gantt');
 

Sch.gantt.Tooltip = function(config, grid) {
    Ext.apply(this, config);
    this.grid = grid;
    Sch.gantt.Tooltip.superclass.constructor.call(this);
};
 
Ext.extend(Sch.gantt.Tooltip, Ext.ToolTip, {
    
    
    startText : 'Starts: ',
    
    
    endText : 'Ends: ',
    
    
    durationText : 'Duration:',
    
    
    dayText : 'd',
    
    
    mode : 'startend', 

    initComponent : function() {    
       
        if (this.mode === 'startend' && !this.startEndTemplate) {
            this.startEndTemplate = new Ext.Template(
                '<div class="sch-timetipwrap {cls}">' +
                '<div>' +
                    this.startText + '{startText}' +
                '</div>' +
                '<div>' +
                    this.endText + '{endText}' +
                '</div>' +
            '</div>'
            ).compile();
        }

        if (this.mode === 'duration' && !this.durationTemplate) {
            this.durationTemplate = new Ext.Template(
                '<div class="sch-timetipwrap {cls}">',
                    '<div>' + this.startText + ' {startText}</div>',
                    '<div>' + this.durationText + ' {duration} ' + this.dayText + '</div>',
                '</div>'
            ).compile();
        }
        
        Sch.gantt.Tooltip.superclass.initComponent.apply(this, arguments);
    },
    
    cls : 'sch-tip',
    width: 145,
    height:40,
    autoHide : false,
    anchor : 'b-tl',

    update : function(start, end, valid) {
        var content;
        if (this.mode === 'duration') {
            content = this.getDurationContent(start, end, valid);
        } else {
            content = this.getStartEndContent(start, end, valid);
        }
        Sch.gantt.Tooltip.superclass.update.call(this, content);
    },
     
    // private
    getStartEndContent : function(start, end, valid) {
        var g = this.grid,
            startText = g.getFormattedDate(start),
            roundedEnd,
            endText = startText;
        
        if (end - start > 0) {
            endText = g.getFormattedEndDate(end, start);
        }
        
        var retVal = {
            cls : valid ? 'sch-tip-ok' : 'sch-tip-notok',
            startText : startText,
            endText : endText
        };
        
        if (this.showClock) {
            Ext.apply(retVal, {
                startHourDegrees : roundedStart.getHours() * 30, 
                startMinuteDegrees : roundedStart.getMinutes() * 6
            });
            
            if (end) {
                Ext.apply(retVal, {
                    endHourDegrees : roundedEnd.getHours() * 30, 
                    endMinuteDegrees : roundedEnd.getMinutes() * 6
                });
            }
        }
        return this.startEndTemplate.apply(retVal);
    },
    
     getDurationContent : function(start, end, valid) {
        var g = this.grid,
            formattedStart = g.getFormattedDate(start),
            duration = g.getDuration(start, end);
        
        return this.durationTemplate.apply({
            cls : valid ? 'sch-tip-ok' : 'sch-tip-notok',
            startText : formattedStart,
            duration : duration
        });
    },

    show : function(el) {
        this.initTarget(el);

        // Rendering is weird if the initial tooltip is empty, prepopulate it with some dummy html
        if (!this.rendered) {
            this.render(Ext.getBody());
            var dummyDate = new Date();
            this.html = this.update(dummyDate, dummyDate, true);
        }
        Sch.gantt.Tooltip.superclass.show.call(this);
    }
}); 



 
function ganttDiagnostics() {
    var log;
    if (console && console.log) {
        log = console.log;
    } else {
        if (!window.ganttDebugWin) {
            window.ganttDebugWin = new Ext.Window({
                height:400,
                width: 500,
                bodyStyle:'padding:10px',
                closeAction : 'hide',
                autoScroll:true
            });
        }
        window.ganttDebugWin.show();
        window.ganttDebugWin.update('');
        log = function(text){ window.ganttDebugWin.update((window.ganttDebugWin.body.dom.innerHTML || '') + text + '<br/>');};
    }

    var els = Ext.select('.sch-ganttpanel');
    
    if (els.getCount() === 0) {
        log('No gantt component found');
        return;
    }
    
    var gantt = Ext.getCmp(els.elements[0].id),
        ts = gantt.taskStore,
        ds = gantt.dependencyStore;
    log('Gantt initial config:');
    log(gantt.initialConfig);
    log(gantt.el.select('*').getCount() + ' DOM elements in the gant component');
    log('Gantt view start: ' + gantt.getStart() + ', end: ' + gantt.getEnd());
    
    if (!ts) { log('No task store configured'); return; }
    if (!ds) {log('No dependency store configured'); }
    
    log(ts.getCount() + ' records in the resource store'); 
    log(ds.getCount() + ' records in the dependency store'); 
    log(Ext.select(gantt.eventSelector).getCount() + ' events present in the DOM'); 
    
    if (ts.getCount() > 0) {
        if (!ts.getAt(0).get('StartDate') || !(ts.getAt(0).get('StartDate') instanceof Date)) {
            log ('The store reader is misconfigured - The StartDate field is not setup correctly, please investigate');
            return;
        }
        
        if (!ts.getAt(0).get('EndDate') || !(ts.getAt(0).get('EndDate') instanceof Date)) {
            log('The store reader is misconfigured - The EndDate field is not setup correctly, please investigate');
            return;
        }
        
        if (!ts.fields.get('Id')) {
            log('The store reader is misconfigured - The Id field is not present');
            return;
        }
        
        log('Records in the task store:');
        ts.each(function(r, i) {
            log((i + 1) + '. Start:' + r.get('StartDate') + ', End:' + r.get('EndDate') + ', Id:' + r.get('Id'));
        });
    } else {
        log('Event store has no data.');
    }
    
    if (ds && ds.getCount() > 0) {
        log('Records in the dependency store:');
        ds.each(function(r) {
            log('From:' + r.get('From') + ', To:' + r.get('To') + ', Type:' + r.get('Type'));
            return;
        });
    }
    
    log('Everything seems to be setup ok!');
}

 
Ext.ns('Sch.gantt');

Sch.gantt.ViewPresets = {
    weekAndDayLetter : {
        timeColumnWidth : 140,
        displayDateFormat : 'Y-m-d',
        shiftUnit : "WEEK",
        shiftIncrement : 1,
        defaultSpan : 10,       // By default, show 10 weeks
        timeResolution : {
            unit : "DAY",
            increment : 1
        },
        headerConfig : {
             bottom : {
                unit : "WEEK",
                increment : 1,
                renderer : Sch.util.headerRenderers.dayLetter
            },
            middle : {
                unit : "WEEK",
                dateFormat : 'D d M Y',
                align : 'left'
            }
        }
    },
    weekDateAndMonth : {
        timeColumnWidth : 30,
        displayDateFormat : 'Y-m-d',
        shiftUnit : "WEEK",
        shiftIncrement : 1,
        defaultSpan : 10,       // By default, show 10 weeks
        timeResolution : {
            unit : "DAY",
            increment : 1
        },
        headerConfig : {
             middle : {
                unit : "WEEK",
                dateFormat : 'd'
            },
            top : {
                unit : "MONTH",
                dateFormat : 'Y F',
                align : 'left'
            }
        }
    }
};

// Register all the basic presets with the Preset Manager
(function() {
    var pm = Sch.PresetManager,
        vp = Sch.gantt.ViewPresets;

    for (var p in vp) {
        if (vp.hasOwnProperty(p)) {
            pm.registerPreset(p, vp[p]);
        }
    }
})();


Ext.ns('Sch.gantt');


Sch.gantt.GanttView = Ext.extend(Sch.AbstractLockingSchedulerView, {
    
    // private
	constructor : function() {
	    this.addEvents(
	        
	        'togglerow'
	    );
        Sch.gantt.GanttView.superclass.constructor.apply(this, arguments);
	},

    // private
    timelineRowTpl : new Ext.Template('<div class="x-grid3-row x-unselectable {alt}" style="{tstyle} {display_style}">{value}</div>'),
	
    // private
	doRender : function(cs, rs, ds, startRow, colCount, stripe){
		var ts = this.templates, ct = ts.cell, rt = ts.row, last = colCount-1,
			tstyle = 'width:'+this.getTotalWidth()+';',
			lstyle = 'width:'+this.getLockedWidth()+';',
			lbuf = [], buf = [], lcb, c, p = {}, rp = {}, r, events, processed_cnt = 0;

        colCount = this.cm.getLockedCount();

		for(var j = 0, len = rs.length; j < len; j++){
			r = rs[j]; cb = []; lcb = [];
			var rowIndex = (j+startRow);
			
			for(var i = 0; i < colCount; i++){
				c = cs[i];
				p.id = c.id;
				p.css = (i === 0 ? 'x-grid3-cell-first ' : (i == last ? 'x-grid3-cell-last ' : '')) +
                    (this.cm.config[i].cellCls ? ' ' + this.cm.config[i].cellCls : '');
				p.attr = p.cellAttr = '';
				
                p.value = c.renderer.call(c.scope || c, r.data[c.name], p, r, rowIndex, i, ds);
				p.style = c.style;
				if(Ext.isEmpty(p.value)){
					p.value = '&#160;';
				}
				if(this.markDirty && r.dirty && Ext.isDefined(r.modified[c.name])){
					p.css += ' x-grid3-dirty-cell';
				}
				// ----- Maxim Modification start
                if (c.id === this.grid.master_column_id) {
                    var depth = ds.getNodeDepth(r);
                    p.treeui = this.renderCellTreeUI(r, ds, depth);
                    p.css += (' sch-gantt-tree-level-' + depth) + (ds.isLeafNode(r) ? ' sch-gantt-leaf-cell' : ' sch-gantt-parent-cell');
                    ct = ts.mastercell;             
                }
                else {
                    ct = ts.cell;
                }
                // ----- Maxim End of modification
				lcb[lcb.length] = ct.apply(p);

			}
			var alt = [];
			
			// ----- Maxim Modification start
            if (!ds.isVisibleNode(r)) {
                rp.display_style = 'display: none;';
            }
            else {
                if(stripe && ((processed_cnt+1) % 2 === 0)){
				    alt[0] = 'x-grid3-row-alt';
			    }
                processed_cnt++;
                rp.display_style = '';
            }
            
            // ----- Maxim End of modification
            
			if(r.dirty){
				alt[1] = ' x-grid3-dirty-row';
			}
			rp.cols = colCount;
			if(this.getRowClass){
				alt[2] = this.getRowClass(r, rowIndex, rp, ds);
			}

			rp.alt = alt.join(' ');
			rp.tstyle = tstyle;
            rp.value = this.timeColumnRenderer(r, ds);
			buf[buf.length] = this.timelineRowTpl.apply(rp);
			
            rp.cells = lcb.join('');
			rp.tstyle = lstyle;
			lbuf[lbuf.length] = rt.apply(rp);
		}
		return [buf.join(''), lbuf.join('')];
	},
	
	// Maxim Tree Grid
	expanded_icon_class : 'ux-maximgb-tg-elbow-minus',
    collapsed_icon_class : 'ux-maximgb-tg-elbow-plus',
    levelWidth : 16,
    
    // private - overriden
    initTemplates : function() {
        var ts = this.templates || {};
        
        if (!ts.row) {
            ts.row = new Ext.Template(
                '<div class="x-grid3-row {alt}" style="{tstyle} {display_style}">',
                    '<table class="x-grid3-row-table" border="0" cellspacing="0" cellpadding="0" style="{tstyle}">',
                        '<tbody>',
                            '<tr>{cells}</tr>',
                        '</tbody>',
                    '</table>',
                '</div>'
            );
        }
        
        if (!ts.mastercell) {
            ts.mastercell = new Ext.Template(
                '<td class="x-grid3-col x-grid3-cell x-grid3-td-{id} {css}" style="{style}" tabIndex="0" {cellAttr}>',
                    '<div class="ux-maximgb-tg-mastercell-wrap">', // This is for editor to place itself right
                        '{treeui}',
                        '<div class="x-grid3-cell-inner x-grid3-col-{id}" unselectable="on" {attr}>{value}</div>',
                    '</div>',
                '</td>'
            );
        }
        
        if (!ts.treeui) {
            ts.treeui = new Ext.Template(
                '<div class="ux-maximgb-tg-uiwrap" style="width: {wrap_width}px">',
                    '<div class="{cls}">&#160;</div>',
                '</div>'
            );
        }
        
        this.templates = ts;
        Sch.gantt.GanttView.superclass.initTemplates.call(this);
    },
    
    // private
    renderCellTreeUI : function(record, store, depth) {
        var tpl = this.templates.treeui,
            tpl_data = {};
        
        tpl_data.wrap_width = (depth + 1) * this.levelWidth; 
        if (store.isLeafNode(record)) {
            tpl_data.cls = 'ux-maximgb-tg-elbow sch-gantt-leaf';
        }
        else {
            tpl_data.cls = 'ux-maximgb-tg-elbow-active sch-gantt-parent ';
            if (store.isExpandedNode(record)) {
                tpl_data.cls += this.expanded_icon_class;
            }
            else {
                tpl_data.cls += this.collapsed_icon_class;
            }
        }
        tpl_data.left = 1 + depth * this.levelWidth;
            
        return tpl.apply(tpl_data);
    },
    
    // private
    processRows : function(startRow, skipStripe) {
        if(!this.ds || this.ds.getCount() < 1){
            return;
        }
        var rows = this.getRows(),
            lrows = this.getLockedRows(),
            row, lrow, processed_cnt = 0;
        skipStripe = skipStripe || !this.grid.stripeRows;
        startRow = startRow || 0;
        for(var i = 0, len = rows.length; i < len; ++i){
            row = rows[i];
            lrow = lrows[i];
            row.rowIndex = i;
            lrow.rowIndex = i;
            
            if (row.style.display != 'none') {
                if(!skipStripe){
                    row.className = row.className.replace(this.rowClsRe, ' ');
                    lrow.className = lrow.className.replace(this.rowClsRe, ' ');
                    
                    if ((processed_cnt + 1) % 2 === 0){
                        row.className += ' x-grid3-row-alt';
                        lrow.className += ' x-grid3-row-alt';
                    }
                    
                    processed_cnt++;
                }
            }
            
            if(this.syncHeights){
                var el1 = Ext.get(row),
                    el2 = Ext.get(lrow),
                    h1 = el1.getHeight(),
                    h2 = el2.getHeight();
                
                if(h1 > h2){
                    el2.setHeight(h1);    
                }else if(h2 > h1){
                    el1.setHeight(h2);
                }
            }
        }
        if(startRow === 0){
            Ext.fly(rows[0]).addClass(this.firstRowCls);
            Ext.fly(lrows[0]).addClass(this.firstRowCls);
        }
        Ext.fly(rows[rows.length - 1]).addClass(this.lastRowCls);
        Ext.fly(lrows[lrows.length - 1]).addClass(this.lastRowCls);
    },
    
    ensureVisible : function(row, col, hscroll) {
        var ancestors, record = this.ds.getAt(row);
        
        if (!this.ds.isVisibleNode(record)) {
            ancestors = this.ds.getNodeAncestors(record);
            while (ancestors.length > 0) {
                record = ancestors.shift();
                if (!this.ds.isExpandedNode(record)) {
                    this.ds.expandNode(record);
                }
            }
        }
        
        return Sch.gantt.GanttView.superclass.ensureVisible.call(this, row, col, hscroll);
    },
    
    // Private
    expandRow : function(record, skip_process) {
        var ds = this.ds,
            i, len, row, pmel, children, index, child_index;
        
        if (typeof record == 'number') {
            index = record;
            record = ds.getAt(index);
        }
        else {
            index = ds.indexOf(record);
        }
        
        if (ds.isLeafNode(record)) return;
        
        skip_process = skip_process || false;
        
        row = this.getLockedRow(index);
        pmel = Ext.fly(row).child('.ux-maximgb-tg-elbow-active');
        if (pmel) {
            pmel.removeClass(this.collapsed_icon_class);
            pmel.addClass(this.expanded_icon_class);
        }
        if (ds.isVisibleNode(record)) {
            children = ds.getNodeChildren(record);
            for (i = 0, len = children.length; i < len; i++) {
                child_index = ds.indexOf(children[i]);
                this.getRow(child_index).style.display = 'block';
                this.getLockedRow(child_index).style.display = 'block';
                
                if (ds.isExpandedNode(children[i])) {
                    this.expandRow(child_index, true);
                }
            }
        }
        if (!skip_process) {
            this.processRows(0);
        }
        
        this.fireEvent('togglerow', this, record, true);
    },
    
    // private
    collapseRow : function(record, skip_process) {
        var ds = this.ds,
            i, len, children, pmel, row, index, child_index;
        
                
        if (typeof record == 'number') {
            index = record;
            record = ds.getAt(index);
        }
        else {
            index = ds.indexOf(record);
        }
        
        if (ds.isLeafNode(record)) return;
        
        skip_process = skip_process || false;
        
        row = this.getLockedRow(index);
        pmel = Ext.fly(row).child('.ux-maximgb-tg-elbow-active');
        if (pmel) {
            pmel.removeClass(this.expanded_icon_class);
            pmel.addClass(this.collapsed_icon_class);
        }
        children = ds.getNodeChildren(record);
        for (i = 0, len = children.length; i < len; i++) {
            child_index = ds.indexOf(children[i]);
            row = this.getRow(child_index);
            if (row.style.display != 'none') {
                row.style.display = 'none'; 
                this.getLockedRow(child_index).style.display = 'none'; 
                this.collapseRow(child_index, true);
            }
        }
        if (!skip_process) {
            this.processRows(0);
        }
        this.fireEvent('togglerow', this, record, true);
    },
    
    // private
    initData : function(ds, cm) {
		Sch.gantt.GanttView.superclass.initData.call(this, ds, cm);
		
        if (this.ds) {
            this.ds.un('expandnode', this.onStoreExpandNode, this);
            this.ds.un('collapsenode', this.onStoreCollapseNode, this);
        }
        if (ds) {
            ds.on({
                expandnode : this.onStoreExpandNode,
                expandingnode : this.onStoreStartExpand,
                expandnodefailed : this.onStoreExpandNodeFailed,
                collapsenode : this.onStoreCollapseNode,
                scope : this
            });
        }
    },
    
    // Before remote request
    // private
    onStoreStartExpand : function(s, record) {
        var index = s.indexOf(record);
        if (index >= 0) {
            var lockedRow = this.getLockedRow(index);
            Ext.fly(lockedRow).child('.' + this.collapsed_icon_class).addClass('sch-loading');
        }
    },
    
    // After failed remote request
    onStoreExpandNodeFailed : function(s, n, record) {
        var index = s.indexOf(record);
        if (index >= 0) {
            var lockedRow = this.getLockedRow(index);
            Ext.fly(lockedRow).child('.' + this.collapsed_icon_class).removeClass('sch-loading');
        }
    },
    
    // private
    onLoad : function(store, records, options) {
        if (options && 
            options.params && 
            (
                options.params[store.paramNames.active_node] === null ||
                store.indexOfId(options.params[store.paramNames.active_node]) == -1
            )
        ) {
            Sch.gantt.GanttView.superclass.onLoad.call(this, store, records, options);
        }
    },
    
    // private
    onAdd : function(ds, records, index) {
        Sch.gantt.GanttView.superclass.onAdd.call(this, ds, records, index);
        if (this.mainWrap) {
           this.processRows(0);
        }
    },
    
    // private
    onRemove : function(ds, record, index, isUpdate) {
        Sch.gantt.GanttView.superclass.onRemove.call(this, ds, record, index, isUpdate);
         
        // Check if the remove made the parent a leaf? 
        var parent = ds.getNodeParent(record);
        
        if (parent) {
            parent.set(ds.leaf_field_name, !ds.hasChildNodes(parent));
        }
        
        if(isUpdate !== true){
            if (this.mainWrap) {
                this.processRows(0);
            }
        }
    },
    
    // private
    onUpdate : function(ds, record) {
        Sch.gantt.GanttView.superclass.onUpdate.call(this, ds, record);
        if (this.mainWrap) {
            this.processRows(0);
        }
    },
    
    refreshRow : function(record) {
        Sch.gantt.GanttView.superclass.refreshRow.call(this, record);
        if (this.mainWrap) {
            this.processRows(0);
        }
    },
    
    // private
    onStoreExpandNode : function(store, record, isAsync) {
        // If this was a remote request, remove the load indicator
        if (isAsync) {
            var index = store.indexOf(record);
            if (index >= 0) {
                var lockedRow = this.getLockedRow(index);
                Ext.fly(lockedRow).child('.' + this.collapsed_icon_class).removeClass('sch-loading');
            }
        }
        this.expandRow(record);
    },
    
    // private
    onStoreCollapseNode : function(store, rc) {
        this.collapseRow(rc);
    },

    
    focusCell : function(row, col, hscroll) {
        this.syncFocusEl.call(this, this.ensureVisible(row, col, hscroll), col < this.cm.getLockedCount());
        
        var focusEl = this.focusEl;
        
        if (Ext.isGecko) {
            focusEl.focus();
        } else {
            focusEl.focus.defer(1, focusEl);
        }
    },


    // private
    syncFocusEl : function(row, col, hscroll) {
        if (col === undefined) {
            this.focusEl.setXY(this.mainBody.getXY());
            return;
        }

        if (Ext.isArray(row)) {
            if (col) {  // true if it's a locked column
                this.focusEl.setXY([this.mainBody.getX() + this.scroller.getScroll().left, row[1]]); // TODO
                return;
            }
        }
        
        Sch.gantt.GanttView.superclass.syncFocusEl.apply(this, arguments);
    },

    // private
    timeColumnRenderer : function(taskRecord, ds) {
        var taskStart = taskRecord.get('StartDate');
        
        if (!taskStart) {
            // No date data to render
            return '';
        }

        var cellResult = '',
            taskEnd = taskRecord.get('EndDate') || Sch.util.Date.add(taskStart, Sch.util.Date.DAY, 1),
            grid = this.grid,
            viewStart = grid.getStart(),
            viewEnd = grid.getEnd(),
            doRender = Sch.util.Date.intersectSpans(taskStart, taskEnd, viewStart, viewEnd);
        
        if (doRender) {

            var startsInsideView = Sch.util.Date.betweenLesser(taskStart, viewStart, viewEnd),
                taskStartX = this.getXFromDate(startsInsideView ? taskStart : viewStart),
                // Get data from user "renderer" 
                userData = grid.eventRenderer.call(this, taskRecord, ds) || {},
                lField = grid.leftLabelField,
                rField = grid.rightLabelField,
                tpl,
                tplData = {
                    // Core properties
                    id : grid.eventPrefix + taskRecord.id,
                    leftOffset : taskStartX,
                    internalcls : (taskRecord.dirty ? ' sch-dirty' : ''),
                    
                    // Percent complete
                    percentDone : taskRecord.get('PercentDone') || 0,

                    // Labels
                    leftLabel : lField.renderer.call(lField.scope || this, taskRecord.data[lField.dataIndex], taskRecord),
                    rightLabel : rField.renderer.call(rField.scope || this, taskRecord.data[rField.dataIndex], taskRecord)
                };

            Ext.apply(tplData, userData);
                
            if (grid.isMilestone(taskRecord)) {
                tpl = grid.milestoneTemplate;
            } else {
                var endsOutsideView = taskEnd > viewEnd,
                    itemWidth = Math.floor(this.getXFromDate(endsOutsideView ? viewEnd : taskEnd)) - taskStartX,
                    isLeaf = grid.taskStore.isLeafNode(taskRecord);
                
                tplData.width = Math.max(1, Ext.isBorderBox ? itemWidth : itemWidth - grid.eventBorderWidth);
                
                if (endsOutsideView) {
                    tplData.internalcls += ' sch-event-endsoutside ';
                }

                if (!startsInsideView) {
                    tplData.internalcls += ' sch-event-startsoutside ';
                }
                tpl = grid[isLeaf ? "eventTemplate" : "parentEventTemplate"];
            }

            if (grid.showBaseline) {
                var baseStartX = this.getXFromDate(taskRecord.get('BaselineStartDate')),
                    baseWidth = Math.floor(this.getXFromDate(taskRecord.get('BaselineEndDate'))) - baseStartX;
                
                Ext.apply(tplData, {
                    baseLeftOffset : baseStartX,
                    baseWidth : Math.max(1, Ext.isBorderBox ? baseWidth : baseWidth - grid.eventBorderWidth)
                });
            }

            cellResult += tpl.apply(tplData);
        }
        
        return cellResult;
    },

    updateAllColumnWidths : function(){
        var tw = this.getTotalWidth(),
            clen = this.cm.getColumnCount(),
            lw = this.getLockedWidth(),
            llen = this.cm.getLockedCount(),
            ws = [], len, i;
        this.updateLockedWidth();
        
        for(i = 0; i < clen; i++){
            ws[i] = this.getColumnWidth(i);
            var hd = this.getHeaderCell(i);
            hd.style.width = ws[i];
        }
        var lns = this.getLockedRows(), ns = this.getRows(), row, trow, j;
        for(i = 0, len = ns.length; i < len; i++){
            row = lns[i];
            row.style.width = lw;
            if(row.firstChild){
                row.firstChild.style.width = lw;
                trow = row.firstChild.rows[0];
                for (j = 0; j < llen; j++) {
                   trow.childNodes[j].style.width = ws[j];
                }
            }
            row = ns[i];
            row.style.width = tw;
        }
        this.onAllColumnWidthsUpdated(ws, tw);
        this.syncHeaderHeight();
    }
});

// Adaptations for LockingGridView changes in Ext 3.3
if (Ext.getMajorVersion() >= 3 && Ext.getMinorVersion() >= 3) {
    Ext.override(Sch.gantt.GanttView, {
	    refreshRow : function(record) {
            var store     = this.ds,
                colCount  = this.cm.getLockedCount(),
                columns   = this.getColumnData(),
                last      = colCount - 1,
                cls       = ['x-grid3-row'],
                rowParams = {
                    tstyle: String.format("width: {0};", this.getTotalWidth())
                },
                lockedRowParams = {
                    tstyle: String.format("width: {0};", this.getLockedWidth())
                },
                colBuffer = [],
                lockedColBuffer = [],
                cellTpl   = this.templates.cell,
                mastercellTpl   = this.templates.mastercell,
                rowIndex, row, lockedRow, column, meta, css, i;
            
            if (Ext.isNumber(record)) {
                rowIndex = record;
                record   = store.getAt(rowIndex);
            } else {
                rowIndex = store.indexOf(record);
            }
            
            if (!record || rowIndex < 0) {
                return;
            }
            
            for (i = 0; i < colCount; i++) {
                column = columns[i];
                
                if (i == 0) {
                    css = 'x-grid3-cell-first';
                } else {
                    css = (i == last) ? 'x-grid3-cell-last ' : '';
                }
                
                meta = {
                    id      : column.id,
                    style   : column.style,
                    css     : css,
                    attr    : "",
                    cellAttr: ""
                };
                
                meta.value = column.renderer.call(column.scope || column, record.data[column.name], meta, record, rowIndex, i, store);
                
                if (Ext.isEmpty(meta.value)) {
                    meta.value = '&#160;';
                }
                
                if (this.markDirty && record.dirty && typeof record.modified[column.name] != 'undefined') {
                    meta.css += ' x-grid3-dirty-cell';
                }
                
                if (column.id === this.grid.master_column_id) {
                    var depth = store.getNodeDepth(record);
                    meta.treeui = this.renderCellTreeUI(record, store, depth);
                    meta.css += (' sch-gantt-tree-level-' + depth) + (store.isLeafNode(record) ? ' sch-gantt-leaf-cell' : ' sch-gantt-parent-cell');
                    lockedColBuffer[i] = mastercellTpl.apply(meta);
                } else {
                    lockedColBuffer[i] = cellTpl.apply(meta);
                }
            }

            row = this.getRow(rowIndex);
            row.className = '';
            lockedRow = this.getLockedRow(rowIndex);
            lockedRow.className = '';
            
            if (this.grid.stripeRows && ((rowIndex + 1) % 2 === 0)) {
                cls.push('x-grid3-row-alt');
            }
            
            if (this.getRowClass) {
                rowParams.cols = colCount;
                cls.push(this.getRowClass(record, rowIndex, rowParams, store));
            }
            
            // Unlocked rows
            this.fly(row).addClass(cls).setStyle(rowParams.tstyle);
            row.innerHTML = this.timeColumnRenderer(record, store);
            
            // Locked rows
            this.fly(lockedRow).addClass(cls).setStyle(lockedRowParams.tstyle);
            lockedRowParams.cells = lockedColBuffer.join("");
            lockedRow.innerHTML = this.templates.rowInner.apply(lockedRowParams);
            lockedRow.rowIndex = rowIndex;
            
            this.fireEvent('rowupdated', this, rowIndex, record);
        }
    });
}




Ext.ns('Sch.gantt');

if (Ext.ux && Ext.ux.grid && Ext.ux.grid.BufferView) {
    
    Sch.gantt.BufferedGanttView = Ext.extend(Sch.gantt.GanttView, {
	    
	    rowHeight: 22,

	    
	    borderHeight: 0,

	    
	    scrollDelay: Ext.ux.grid.BufferView.prototype.scrollDelay,

	    
	    cacheSize: Ext.ux.grid.BufferView.prototype.cacheSize,

	    
	    cleanDelay: Ext.ux.grid.BufferView.prototype.cleanDelay,
    
        // private
        isBuffered : true,

        constructor : function() {
		    Sch.gantt.BufferedGanttView.superclass.constructor.apply(this, arguments);
            this.calculatedRowHeight = this.rowHeight + this.borderHeight;
            this.styleRowHeight = Ext.isBorderBox ? (this.rowHeight + this.borderHeight) : this.rowHeight;
            this.on('togglerow', this.update, this);
        },
    
	    initTemplates : function(){
		    Sch.gantt.BufferedGanttView.superclass.initTemplates.call(this);
		    var ts = this.templates;
	    
		    // empty div to act as a place holder for a row
	        ts.rowHolder = new Ext.Template(
		        '<div class="x-grid3-row {alt}" style="{tstyle};{display_style}"></div>'
		    );
		    ts.rowHolder.disableFormats = true;
		    ts.rowHolder.compile();

		    ts.rowBody = new Ext.Template(
		        '<table class="x-grid3-row-table" border="0" cellspacing="0" cellpadding="0" style="{tstyle}">',
			    '<tbody><tr>{cells}</tr>',
			    (this.enableRowBody ? '<tr class="x-grid3-row-body-tr" style="{bodyStyle}"><td colspan="{cols}" class="x-grid3-body-cell" tabIndex="0" hidefocus="on"><div class="x-grid3-row-body">{body}</div></td></tr>' : ''),
			    '</tbody></table>'
		    );
		    ts.rowBody.disableFormats = true;
		    ts.rowBody.compile();
	    },

	    getVisibleRowCount : function(){
		    var rh = this.calculatedRowHeight,
		        visibleHeight = this.scroller.dom.clientHeight;
		    return (visibleHeight < 1) ? 0 : Math.ceil(visibleHeight / rh);
	    },

	    getVisibleRows: function(){
		    var count = this.getVisibleRowCount(),
                ds = this.ds,
		        sc = this.scroller.dom.scrollTop,
		        startRow = Math.max((sc === 0 ? 0 : Math.floor(sc/this.calculatedRowHeight)-1), 0),
	            nbrVisible = 0,
	            nbrRecords = ds.getCount(),
	            i = 0;
	        
	        // Find start row
	        while (nbrVisible < startRow && i < nbrRecords) {
	            if (ds.isVisibleNode(ds.getAt(i))) {
	                nbrVisible++;
	            }
	            i++;
	        }
	        startRow = i;
	    
	        // Find end row
	        nbrVisible = 0;
	        count += 2; // Render 2 extra rows
	    
	        while (nbrVisible < count && i < nbrRecords) {
	            if (ds.isVisibleNode(ds.getAt(i))) {
	                nbrVisible++;
	            }
	            i++;
	        }
	    
		    return {
			    first: startRow,
			    last: Math.min(i, nbrRecords-1)
		    };
	    },
    
        doRender : function(cs, rs, ds, startRow, colCount, stripe, onlyBody){
            var me = this,
                rh = me.styleRowHeight,
		        vr = me.getVisibleRows(),
		        vCount = me.getVisibleRowCount(),
                ts = me.templates, 
                ct = ts.cell, 
                rt = ts.row, 
                last = colCount-1, 
                rb = ts.rowBody,
			    tstyle = 'width:'+me.getTotalWidth()+';height:'+rh+'px;',
			    lstyle = 'width:'+me.getLockedWidth()+';height:'+rh+'px;',
                g = me.grid,
                cm = me.cm,
                processed_cnt = 0,
                timelineRowTpl = me.timelineRowTpl,
			    buf = [], 
                lbuf = [], 
                lcb, 
                c, 
                p = {}, 
                rp = {}, 
                r;
		
            colCount = me.cm.getLockedCount();

		    for(var j = 0, len = rs.length; j < len; j++){
			    r = rs[j]; cb = []; lcb = [];
			    var rowIndex = (j+startRow),
			        visible = (rowIndex >= vr.first && rowIndex <= vr.last),
			        treeVisible = ds.isVisibleNode(r);
			
			    if (visible && treeVisible) {
			        for(var i = 0; i < colCount; i++){
				        c = cs[i];
				        p.id = c.id;
				        p.css = (i === 0 ? 'x-grid3-cell-first ' : (i == last ? 'x-grid3-cell-last ' : '')) +
                            (cm.config[i].cellCls ? ' ' + cm.config[i].cellCls : '');
				        p.attr = p.cellAttr = '';
				
                        p.value = c.renderer.call(c.scope || c, r.data[c.name], p, r, rowIndex, i, ds);
				        p.style = c.style;
				        if(Ext.isEmpty(p.value)){
					        p.value = '&#160;';
				        }
				        if(me.markDirty && r.dirty && Ext.isDefined(r.modified[c.name])){
					        p.css += ' x-grid3-dirty-cell';
				        }
				        // ----- Maxim Modification start
                        if (c.id === g.master_column_id) {
                            var depth = ds.getNodeDepth(r);
                            p.treeui = this.renderCellTreeUI(r, ds, depth);
                            p.css += (' sch-gantt-tree-level-' + depth) + (ds.isLeafNode(r) ? ' sch-gantt-leaf-cell' : ' sch-gantt-parent-cell');
                            ct = ts.mastercell;             
                        }
                        else {
                            ct = ts.cell;
                        }
                        // ----- Maxim End of modification
				        lcb[lcb.length] = ct.apply(p);

			        }
                }
			    var alt = [];
			
			    // ----- Maxim Modification start
                if (!treeVisible) {
                    rp.display_style = 'display: none;';
                }
                else {
                    if(stripe && ((processed_cnt+1) % 2 === 0)){
				        alt[0] = 'x-grid3-row-alt';
			        }
                    processed_cnt++;
                    rp.display_style = '';
                }
            
                // ----- Maxim End of modification
            
			    if(r.dirty){
				    alt[1] = ' x-grid3-dirty-row';
			    }
			    rp.cols = colCount;
			    if(me.getRowClass){
				    alt[2] = me.getRowClass(r, rowIndex, rp, ds);
			    }

			    rp.alt = alt.join(' ');
			    rp.tstyle = tstyle;
            
                if (visible && treeVisible) {
                    rp.value = this.timeColumnRenderer(r, ds);
			        buf[buf.length] = onlyBody ? rp.value : timelineRowTpl.apply(rp);
                } else {
			        buf[buf.length] = ts.rowHolder.apply(rp);
                }
			
                rp.cells = lcb.join('');
			    rp.tstyle = lstyle;
                lbuf[lbuf.length] = !(visible && treeVisible) ? ts.rowHolder.apply(rp) : (onlyBody ? rb.apply(rp) : rt.apply(rp));
		    }
		    return [buf.join(''), lbuf.join('')];
	    },

	    isRowRendered: Ext.ux.grid.BufferView.prototype.isRowRendered,

	    syncScroll: function(){
            Sch.gantt.BufferedGanttView.superclass.syncScroll.apply(this, arguments);
            var scroll = this.scroller.getScroll().top;

            // prevent sync scroll on horizontal scroll
            if (scroll !== this.lastScrollTop) {
		        this.update();
                this.lastScrollTop = scroll;
            }
	    },

	    update: Ext.ux.grid.BufferView.prototype.update,
    
        onRemove : function(ds, record, index, isUpdate){
            Sch.gantt.BufferedGanttView.superclass.onRemove.apply(this, arguments);
            if(isUpdate !== true){
                this.update();
            }
        },

	    doUpdate: function() {
		    if (this.getVisibleRowCount() > 0) {
			    var me = this,
                    g = me.grid, 
                    cm = g.colModel, 
                    ds = g.getTaskStore(),
	                cs = me.getColumnData(),
	                vr = me.getVisibleRows(),
	                last = vr.last,
	                colCount = cm.getColumnCount(),
	                dpm = me.grid.getDependencyManager(),
                    r,
                    i,
                    visibleRecords = [];
            
			    for (i = vr.first; i <= last; i++) {
                    r = ds.getAt(i);
				    // if row is NOT rendered and is visible, render it
				    if(!me.isRowRendered(i) && ds.isVisibleNode(r)){
					    var html = me.doRender(cs, [r], ds, i, colCount, g.stripeRows, true);
					    me.getRow(i).innerHTML = html[0];
					    me.getLockedRow(i).innerHTML = html[1];
					    visibleRecords.push(r);
				    }
			    }
                dpm.updateDependencies.defer(100, dpm, [visibleRecords]);

			    me.clean();
		    }
	    },

	    // a buffered method to clean rows
	    clean : Ext.ux.grid.BufferView.prototype.clean,

        doClean: function(){
		    if (this.getVisibleRowCount() > 0) {
			    var vr = this.getVisibleRows(),
			        i = 0, 
                    rows = this.getRows(), 
                    lrows = this.getLockedRows();
            
                vr.first -= this.cacheSize;
			    vr.last += this.cacheSize;
			    // if first is less than 0, all rows have been rendered
			    // so lets clean the end...
			    if(vr.first <= 0){
				    i = vr.last + 1;
			    }
			    for(var len = this.ds.getCount(); i < len; i++){
				    // if current row is outside of first and last and
				    // has content, update the innerHTML to nothing
				    if ((i < vr.first || i > vr.last) && rows[i].innerHTML) {
					    rows[i].innerHTML = '';
					    lrows[i].innerHTML = '';
				    }
			    }
		    }
	    },

	    layout: function(){
		    Sch.gantt.BufferedGanttView.superclass.layout.call(this);
		    this.update();
	    }
    });
}




Ext.ns('Sch.gantt');


Sch.gantt.DependencyDragDrop = function(gantt) {
    this.addEvents(
         
        'beforednd', 
        
        
        'dndstart',
        
        
        'drop',

        
        'afterdnd'
    );
    
    var view = gantt.getView();
    
    Ext.apply(this, {
        el : view.mainBody,
        scroller : view.scroller,
        ddGroup : gantt.id + '-sch-dependency-dd',
        gantt : gantt,
        dependencyStore : gantt.getDependencyStore()
    });

    this.setupDragZone();
    this.setupDropZone();

    Sch.gantt.DependencyDragDrop.superclass.constructor.call(this);
};

Ext.extend(Sch.gantt.DependencyDragDrop, Ext.util.Observable, {
    
    
    fromText : 'From: <strong>{0}</strong> {1}<br/>',
    
    
    toText : 'To: <strong>{0}</strong> {1}',
    
    
    startText : 'Start',
    
    
    endText : 'End',
    
    
    useLineProxy : true,
    
    // private, the terminal CSS selector
    terminalSelector : '.sch-gantt-terminal',
    
    isValidDrop : function(fromTaskId, toTaskId) {
        // Make sure a) the drop is not made on the same task, and b) no previous dependency exists between the two tasks
        function find (r) {
            return fromTaskId === toTaskId ||
                   r.get('From') === fromTaskId && r.get('To') === toTaskId ||
                   r.get('To') === fromTaskId && r.get('From') === toTaskId;
        }
        
        return this.dependencyStore.findBy(find) < 0;
    },
    
    destroy : function() {
        this.dragZone.destroy();
        this.dropZone.destroy();

        if (this.lineProxyEl) {
            this.lineProxyEl.destroy();
        }
    },

    initLineProxy : function(sourceEl, isStart) {
        var lpEl = this.lineProxyEl = this.el.createChild({ cls : 'sch-gantt-connector-proxy' }); 

        lpEl.alignTo(sourceEl, isStart ? 'l' : 'r');
        
        Ext.apply(this, {
            containerTop : this.el.getTop(),
            containerLeft : this.el.getLeft(),
            startXY : lpEl.getXY(),
            startScrollLeft : this.scroller.dom.scrollLeft,
            startScrollTop : this.scroller.dom.scrollTop
        });
    },

    updateLineProxy : function(xy) {
        var lineProxy = this.lineProxyEl,
            diffX = xy[0] - this.startXY[0] + this.scroller.dom.scrollLeft - this.startScrollLeft,
            diffY = xy[1] - this.startXY[1] + this.scroller.dom.scrollTop - this.startScrollTop,
            newHeight = Math.max(1, Math.sqrt(Math.pow(diffX, 2) + Math.pow(diffY, 2)) - 2),

            // Calculate new angle relative to start XY
            rad = Math.atan2(diffY, diffX) - (Math.PI/2),
            styleBlob;

        if (Ext.isIE) {
            var cos = Math.cos(rad),
                sin = Math.sin(rad),
                matrixString = 'progid:DXImageTransform.Microsoft.Matrix(sizingMethod="auto expand", M11 = ' + cos + ', M12 = ' + (-sin) + ', M21 = ' + sin + ', M22 = ' + cos + ')';
            
            styleBlob = {
                "height"    : newHeight + 'px',
                "top"       : Math.min(0, diffY) + this.startXY[1]  - this.containerTop + (diffY < 0 ? 2 : 0)  + 'px',
                "left"      : Math.min(0, diffX) + this.startXY[0] - this.containerLeft + (diffX < 0 ? 2 : 0) + 'px',
                "filter"    : matrixString,
                "-ms-filter": matrixString
            };
        } else {
            var rotateString = 'rotate(' + rad + 'rad)';
            
            styleBlob = {
                "height"            : newHeight + 'px',
                "-o-transform"      : rotateString,
                "-webkit-transform" : rotateString,
                "-moz-transform"    : rotateString,
                "transform"         : rotateString
            };
        }
        
        lineProxy.show().setStyle(styleBlob);
    },

    // private
    setupDragZone : function() {
        var me = this,
            g = this.gantt;
        
        // The drag zone behaviour
        this.dragZone = new Ext.dd.DragZone(this.el, {
            ddGroup : this.ddGroup,
            
            onStartDrag : function () {
                this.el.addClass('sch-gantt-dep-dd-dragging');
                me.fireEvent('dndstart', me);

                if (me.useLineProxy) {
                    var dd = this.dragData;
                    me.initLineProxy(dd.sourceNode, dd.isStart);
                }
            },
            
            // On receipt of a mousedown event, see if it is within a draggable element.
            // Return a drag data object if so. The data object can contain arbitrary application
            // data, but it should also contain a DOM element in the ddel property to provide
            // a proxy to drag.
            getDragData: function(e) {
                var sourceNode = e.getTarget(me.terminalSelector);

                if (sourceNode) {
                    var sourceTaskRecord = g.resolveTask(sourceNode);
                    if (me.fireEvent('beforednd', this, sourceTaskRecord) === false) {
                        return null;
                    }
                    
                    var isStart = !!sourceNode.className.match('sch-gantt-terminal-start'),
                        ddel = Ext.DomHelper.createDom({
                            cls: 'sch-dd-dependency',
                            children: [
                                {
                                    tag: 'span', 
                                    cls: 'sch-dd-dependency-from', 
                                    html: String.format(me.fromText, sourceTaskRecord.get('Name'), isStart ? me.startText : me.endText)
                                },
                                {
                                    tag: 'span', 
                                    cls: 'sch-dd-dependency-to', 
                                    html: String.format(me.toText, '', '')
                                }
                            ]
                        });
                    
                    return {
                        fromId : sourceTaskRecord.id,
                        isStart : isStart,
                        repairXY: Ext.fly(sourceNode).getXY(),
                        ddel: ddel,
                        sourceNode : Ext.fly(sourceNode).up(g.eventSelector)
                    };
                }
                return false;
            },
            
            // Override, get rid of weird highlight fx in default implementation
            afterRepair : function(){
                this.el.removeClass('sch-gantt-dep-dd-dragging');
                
                this.dragging = false;
                me.fireEvent('afterdnd', this);
            },

            onMouseUp : function() {
                if (me.lineProxyEl) {
                    if (Ext.isIE) {
                        Ext.destroy(me.lineProxyEl);
                        me.lineProxyEl = null;
                    }
                    else {
                        me.lineProxyEl.setHeight(0, {
                            duration : .3, 
                            callback : function() {
                                Ext.destroy(me.lineProxyEl);
                                me.lineProxyEl = null;
                            }
                        });
                    }
                }
            },

            // Provide coordinates for the proxy to slide back to on failed drag.
            // This is the original XY coordinates of the draggable element.
            getRepairXY: function() {
                return this.dragData.repairXY;
            }
        });
    },
    
    // private
    setupDropZone : function () {
        var me = this,
            g = this.gantt;
                    
        // The drop zone behaviour
        this.dropZone = new Ext.dd.DropZone(this.el, {
            ddGroup : this.ddGroup,
            
            getTargetFromEvent: function(e) {
                if (me.useLineProxy) {
                    me.updateLineProxy(e.getXY());
                }
                return e.getTarget(me.terminalSelector);
            },
            
            // On entry into a target node, highlight that node.
            onNodeEnter : function(target, dd, e, data){
                var isTargetStart = target.className.match('sch-gantt-terminal-start');
                Ext.fly(target).addClass(isTargetStart ? 'sch-gantt-terminal-start-drophover' : 'sch-gantt-terminal-end-drophover');
            },

            // On exit from a target node, unhighlight that node.
            onNodeOut : function(target, dd, e, data){
                var isTargetStart = target.className.match('sch-gantt-terminal-start');
                Ext.fly(target).removeClass(isTargetStart ? 'sch-gantt-terminal-start-drophover' : 'sch-gantt-terminal-end-drophover');
            },
            
            onNodeOver : function(target, dd, e, data){
                var targetRecord = g.resolveTask(target),
                    targetId = targetRecord.id,
                    isTargetStart = target.className.match('sch-gantt-terminal-start'),
                    newText = String.format(me.toText, targetRecord.get('Name'), isTargetStart ? me.startText : me.endText);
                
                dd.proxy.el.child('.sch-dd-dependency-to').update(newText);
                
                if (me.isValidDrop.call(me, data.fromId, targetId)) {
                    return this.dropAllowed;
                } else {
                    return this.dropNotAllowed;
                }
            },
            
            onNodeDrop : function(target, dd, e, data){
                var type, 
                    retVal = true,
                    rec = g.resolveTask(target),
                    targetId = rec.id;
                
                if (me.lineProxyEl) {
                    Ext.destroy(me.lineProxyEl);
                    me.lineProxyEl = null;
                }
                this.el.removeClass('sch-gantt-dep-dd-dragging');
                
                if (data.isStart) {
                    if (target.className.match('sch-gantt-terminal-start')) {
                        type = Sch.gantt.Dependency.StartToStart;
                    } else {
                        type = Sch.gantt.Dependency.StartToEnd;
                    }
                } else {
                    if (target.className.match('sch-gantt-terminal-start')) {
                        type = Sch.gantt.Dependency.EndToStart;
                    } else {
                        type = Sch.gantt.Dependency.EndToEnd;
                    }
                }
               
                retVal = me.isValidDrop.call(me, data.fromId, targetId, true);
                
                if (retVal) {
                    me.fireEvent('drop', this, data.fromId, rec.id, type);
                }
                me.fireEvent('afterdnd', this);
                return retVal;
            }
        });
    }
});

 
Ext.ns('Sch.gantt');


Sch.gantt.Dependency = {
    StartToStart : 0,
    StartToEnd : 1,
    EndToStart : 2,
    EndToEnd : 3
};



Sch.gantt.DependencyManager = Ext.extend(Ext.util.Observable, {
    
    
    cascadeDelay : 10,
    
    
    highlightDependency : function(record) {
        if (!(record instanceof Ext.data.Record)) {
            record = this.store.getById(record);
        }
        this.getElementsForDependency(record).addClass('sch-dependency-selected');
    },
    
    
    unhighlightDependency : function(record) {
        if (!(record instanceof Ext.data.Record)) {
            record = this.store.getById(record);
        }
        this.getElementsForDependency(record).removeClass('sch-dependency-selected');
    },
    
    
    getElementsForDependency : function(rec) {
        var id = rec instanceof Ext.data.Record ? rec.id : rec;
        return this.containerEl.select('.sch-dep-' + id);
    },

    cascading : false,
    
    // private
    constructor : function (g, cfg) {
        cfg = cfg || {};
        Ext.apply(this, cfg);
        var v = g.getView();
        this.grid = g;

        v.on({
            refresh : this.renderDependencies,
            togglerow : this.renderDependencies,
            rowupdated: this.onRowUpdated, 
	        rowsinserted : this.renderDependencies,
            rowremoved : this.onRowDeleted,
            scope : this
        });
        
        this.taskStore = g.getTaskStore();
        this.taskStore.on({
            update: this.onTaskUpdated, 
            beforewrite : this.onTaskStoreBeforeWrite,
            write : this.onTaskStoreWrite,
            scope : this
        });
        
        this.store.on({
            datachanged : this.renderDependencies,
            add : this.onDependencyAdd,
            update : this.onDependencyUpdate,
            remove : this.onDependencyDelete,
            beforewrite : this.onBeforeWrite,
            scope : this
        });
        
        this.painter = new Sch.gantt.DependencyPainter(g, cfg);
        
        this.addEvents(
             
            'beforednd', 
        
            
            'dndstart',
        
             
            'drop',

            
            'afterdnd',

             
            'beforecascade', 
        
            
            'cascade'
        );
        
        if (this.enableDependencyDragDrop !== false) {
            this.dnd = new Sch.gantt.DependencyDragDrop(g);
            this.dnd.on('drop', this.onDependencyDrop, this);
            this.relayEvents(this.dnd, ['beforednd', 'dndstart', 'afterdnd', 'drop']);
        }

        Sch.gantt.DependencyManager.superclass.constructor.call(this, cfg);
    },
    
    // private
    depRe : new RegExp('sch-dep-([^\\s]+)'),
    
    // private
    getRecordForDependencyEl : function(t) {
        var m = t.className.match(this.depRe),
            rec = null;
        
        if(m && m[1]) {
            var recordId = m[1];
            
            rec = this.store.getById(recordId);
        }
        
        return rec;
    },
    
    renderDependencies : function() {
        this.containerEl.select('.sch-dependency').remove();
        this.painter.draw(this.store.getRange());
    },

    renderTaskDependencies : function(records) {
        var dep,
            l = this.store.getCount(),
            id,
            toDraw = [];
        
        if (!Ext.isArray(records)) {
            records = [records];
        }

        for (var i = 0, n = records.length; i < n; i++) {
            id = records[i].id;

            for (var j = 0; j < l; j++) {
                dep = this.store.getAt(j);
                var toEventId = dep.get('To'),
                    fromEventId = dep.get('From');
            
                if (id == toEventId || id == fromEventId) {
                    toDraw.push(dep);
                }
  	        }
        }
        this.painter.draw(toDraw);
    },
    
    onTaskStoreBeforeWrite : function(s, action, records) {
        // Normalize
        if (!Ext.isArray(records)) {
            records = [records];
        }
        
        if (action === Ext.data.Api.actions.create) {
            Ext.each(records, function(r) {
                // HACK, save the phantom id to be able to replace the task phantom task id's in the dependency store
                r._phantomId = r.id;
            });
        }
    },
    
    onTaskStoreWrite : function(s, action, result, t, records) {
        if (!Ext.isArray(records)) {
            records = [records];
        }
        var from, to;
        
        if (action === Ext.data.Api.actions.create) {
            Ext.each(records, function(r) {
                this.store.queryBy(function(dep) {
                    from = dep.get('From');
                    to = dep.get('To');
                    
                    if (from === r._phantomId) {
                        dep.set('From', r.id);
                    } else if (to === r._phantomId){
                        dep.set('To', r.id);
                    }
                });
            }, this);
        }
    },
    
    onDependencyUpdate : function(store, depRecord) {
       if (depRecord._phantomId) {
            // Delete lines drawn for any phantom record.
            this.getElementsForDependency(depRecord._phantomId).remove();
            delete depRecord._phantomId;
        }
       
       this.removeDependencyElements(depRecord, false);
        
       // Draw new dependencies for the event
       this.painter.draw(depRecord);
    },
    
    onDependencyAdd : function(store, depRecords) {
        var dep = depRecords[0];
        
        // Draw new dependencies for the event
        this.painter.draw(dep);
        
        // If cascade changes is activated, adjust the connected task start/end date
        if (this.grid.cascadeChanges) {
            this.constrainTask(this.taskStore.getById(dep.get('To')));
        }
    },
    
    removeDependencyElements : function(record, animate) {
        if (animate !== false) {
            this.getElementsForDependency(record).fadeOut({ remove : true });
        } else {
            this.getElementsForDependency(record).remove();
        }
    },

    onDependencyDelete : function(store, depRecord) {
       this.removeDependencyElements(depRecord);
    },
    
    dimEventDependencies : function(eventId) {
        this.containerEl.select(this.depRe + eventId).setOpacity(0.2);
    },
    
    onBeforeWrite : function(s, action, records) {
        // Normalize
        if (!Ext.isArray(records)) {
            records = [records];
        }
        // Delete lines drawn for the phantom dependency record. After the create operation is finished the lines will be redrawn.
        if (action === Ext.data.Api.actions.create) {
            Ext.each(records, function(r) {
                // HACK, save the phantom id to be able to remove the dom elements associated with the phantom record
                r._phantomId = r.id;
            }, this);
        }
    },
    
    onRowUpdated : function(v, index, record) {
        // Don't do anything while cascading as the view will be fully refreshed after the cascade operation
        if (!this.cascading) {
            this.updateDependencies(record);
        }
    },
    
    onTaskUpdated : function(store, record, operation, hashPrevious) {
        
        if (this.grid.cascadeChanges && hashPrevious) {
            (function(taskId) {
                var dependentTaskRec, dep, i, l;
                this.preCascade();

                for (i = 0, l = this.store.getCount(); i < l; i++) {
                    dep = this.store.getAt(i);
                    if (dep.get('From') == taskId) {
                        dependentTaskRec = this.taskStore.getById(dep.get('To'));
                        
                        if (dependentTaskRec) {
                            this.cascading = true;
                            this.performCascade(dependentTaskRec);
                        }
                    }
                }

                this.postCascade();

            }).defer(this.cascadeDelay, this, [record.id]);
        }
    },

    preCascade : function() {
        this.fireEvent('beforecascade', this);
        this.taskStore.un('update', this.onTaskUpdated, this);
        this.taskStore.suspendEvents(true);
    },

    postCascade : function() {
        this.taskStore.resumeEvents();
        if (this.cascading) {
            this.cascading = false;
            this.grid.getView().refresh();
        }
        this.fireEvent('cascade', this);
        this.taskStore.on('update', this.onTaskUpdated, this);
    },
    
    updateDependencies : function (record) {
        var cEl = this.containerEl,
            taskIds = [];
        
        if (!Ext.isArray(record)) {
            record = [record];
        }

        for (var i = 0, l = record.length; i < l; i++) {
            taskId = record[i].id;
        
            // First remove old dependency dom elements
            this.store.each(function(r) {
                if (r.get('From') == taskId || r.get('To') == taskId) {
                   cEl.select('.sch-dep-' + r.id).remove();
                }
            });
        }
        
        // Draw new dependencies for the task
        this.renderTaskDependencies(record);
    },
    
    onRowDeleted : function(store, index, record) {
        var eventId = record.id,
            toRemove = [];
        
        this.store.queryBy(function(r) {
            if (r.data.To == eventId || r.data.From == eventId) {
                toRemove.push(r);
            }
        });
        
        this.store.suspendEvents();
        this.store.remove(toRemove);
        this.store.resumeEvents();
        
        // Redraw all dependencies
        this.renderDependencies();
    },
    
    onDependencyDrop : function(plugin, fromId, toId, type) {
        if (this.isValidDependency(fromId, toId)) {
            var depRec = new this.store.recordType({
                From : fromId,
                To : toId,
                Type : type
            });
        
            this.store.add(depRec);
        }
    },
    
    getDependenciesForTask : function(record) {
        var taskId = record.id;
        
        return this.store.queryBy(function(r) {
            return r.get('From') == taskId || r.get('To') == taskId;
        });
    },
    
    deleteDependency : function(id) {
        this.store.remove(this.store.getById(id));
    },
    
    performCascade : function(record) {
        var id = record.id;
        
        if (record.store.isLeafNode(record)) {
            this.constrainTask(record);
            this.grid.recalculateParents(record);
        }
        
        this.store.queryBy(function(dep) {
            if (dep.get('From') == id) {
                var toRecord = record.store.getById(dep.get('To'));
                if (toRecord) {
                    this.performCascade(toRecord);
                }
            }
        }, this);
    },
    
    constrainTask : function(task) {
        var es = this.taskStore,
            constrainContext = this.getConstrainContext(task);
        
        if (constrainContext) {
            task.beginEdit();
            task.set('StartDate', constrainContext.startDate);
            task.set('EndDate', constrainContext.endDate);
            task.endEdit();
        }
    },
    
    
    getCriticalPaths : function() {
        // Grab task id's that don't have any "incoming" dependencies
        var nbrTasks = this.taskStore.getCount();
        
        if (nbrTasks <= 0) return [];
        
        var lastTaskEndDate = new Date(0);
        
        this.taskStore.each(function(t){
            lastTaskEndDate = Sch.util.Date.max(t.get('EndDate'), lastTaskEndDate);
        });
        
        var finalTasks = this.taskStore.queryBy(function(t) { return lastTaskEndDate - t.get('EndDate') === 0; }),
            cPaths = [];
        
        finalTasks.each(function(t) {
            cPaths.push(this.getCriticalPathsInternal(t));
        }, this);
        
        return cPaths;
    },
    
    // 
    
    getCriticalPathsInternal : function(task) {
        var cPath = [task],
            ctx = this.getConstrainContext(task);
        
        while(ctx) {
            cPath.push(ctx.constrainingTask);
            ctx = this.getConstrainContext(ctx.constrainingTask);
        }
        
        return cPath;
    },
    
    getConstrainContext : function(task) {
        var incomingTaskDependencies = this.store.queryBy(function(r) { return r.get('To') === task.id; } );
        
        if (incomingTaskDependencies.getCount() === 0) {
            return null;
        }
        
        var es = this.taskStore,
            taskDuration = task.get('EndDate') - task.get('StartDate'),
            earliestStartDate = new Date(0),
            earliestEndDate = new Date(0),
            constrainingTask,
            D = Sch.util.Date;
            
        incomingTaskDependencies.each(function(d) {
            var t = es.getById(d.get('From'));

            if (t) {
                var start = t.get('StartDate'),
                    end = t.get('EndDate');
            
                switch(d.get('Type')) {
                    case Sch.gantt.Dependency.StartToEnd:
                        if (earliestEndDate < start) {
                            earliestEndDate = start;
                            earliestStartDate = D.add(earliestEndDate, D.MILLI, -taskDuration);
                            constrainingTask = t;
                        }
                    break;
                
                    case Sch.gantt.Dependency.StartToStart:
                        if (earliestStartDate < start) {
                            earliestStartDate = start;
                            earliestEndDate = D.add(earliestStartDate, D.MILLI, taskDuration);
                            constrainingTask = t;
                        }
                    break;
                
                    case Sch.gantt.Dependency.EndToStart:
                        if (earliestStartDate < end) {
                            earliestStartDate = end;
                            earliestEndDate = D.add(earliestStartDate, D.MILLI, taskDuration);
                            constrainingTask = t;
                        }
                    break;
                
                    case Sch.gantt.Dependency.EndToEnd:
                        if (earliestEndDate < end) {
                            earliestEndDate = end;
                            earliestStartDate = D.add(earliestEndDate, D.MILLI, -taskDuration);
                            constrainingTask = t;
                        }
                    break;
                
                    default:
                        throw 'Invalid case statement';
                    break;
                }
            }
        });
        
        return {
            startDate : earliestStartDate,
            endDate : earliestEndDate,
            constrainingDate : earliestStartDate,
            constrainingTask : constrainingTask
        };
    },

    destroy : function() {
        this.dnd.destroy();
    },

    isValidDependency : function(fromId, toId) {
        var valid = true,
            ts = this.taskStore,
            fromTask = ts.getById(fromId), 
            toTask = ts.getById(toId);

        if (fromId === toTask) {
            valid = false;
        }
        
        if (valid && ts.isTaskChildOf(fromTask, toTask) ||
            ts.isTaskChildOf(toTask, fromTask)) {
            valid = false;
        } 

        if (valid && this.findToTask(toId, fromId) >= 0) {
            valid = false;
        }
            
        return valid;     
    },

    findToTask : function(sourceId, targetId) {
        return this.store.findBy(function(r) {
            if (r.get('From') === sourceId) {
                if (r.get('To') === targetId) {
                    return true;
                } else {
                    return this.findToTask(r.get('To'), targetId) >= 0; 
                }
            }
        }, this);
    }
});


 
Ext.ns('Sch.gantt');


Sch.gantt.DependencyPainter = Ext.extend(Ext.util.Observable, {
    
    rowHeight : 22,

    constructor : function (g, cfg) {    
        var view = g.getView();

        cfg = cfg || {};
       
        Ext.apply(this, cfg, {
            buffered : view.isBuffered,
            grid : g,
            taskStore : g.getTaskStore(),
            view : view,
            containerEl : view.scroller,
            xOffset : 8,
            yOffset : this.rowHeight/2,
            midRowOffset : 4,
            arrowOffset : 8
        });
        
        if (!this.lineTpl) {
            var line = '<div class="sch-dependency sch-dep-{parent.id} sch-dependency-line" style="left:{[Math.min(values.x1, values.x2)]}px;top:{[Math.min(values.y1, values.y2)]}px;width:{[Math.abs(values.x1-values.x2)]}px;height:{[Math.abs(values.y1-values.y2)]}px"></div>',
                ieLine = '<div class="sch-dependency sch-dep-{parent.id} sch-dependency-line" style="left:{[Math.min(values.x1, values.x2)]}px;top:{[Math.min(values.y1, values.y2)]}px;width:{[Math.abs(values.x1-values.x2)+2]}px;height:{[Math.abs(values.y1-values.y2)+2]}px"></div>';
            
            var arrow = '<img src="' + Ext.BLANK_IMAGE_URL + '" class="sch-dependency sch-dep-{id} sch-dependency-arrow sch-dependency-arrow-{[this.getArrowDirection(values.lineCoordinates)]}" style="left:{[values.lineCoordinates[values.lineCoordinates.length - 1].x2]}px;top:{[values.lineCoordinates[values.lineCoordinates.length - 1].y2]}px"/>';

            var ieArrow = '<div style="left:{[values.lineCoordinates[values.lineCoordinates.length - 1].x2]}px;top:{[values.lineCoordinates[values.lineCoordinates.length - 1].y2]}px" class="sch-dependency-arrow-ct sch-dependency sch-dep-{id} "><img src="' + Ext.BLANK_IMAGE_URL + '" class="sch-dependency-arrow sch-dependency-arrow-{[this.getArrowDirection(values.lineCoordinates)]}" /></div>';

            this.lineTpl = new Ext.XTemplate(
                '<tpl for=".">'+
                    '<tpl for="lineCoordinates">'+
                        (Ext.isBorderBox ? ieLine : line)+
                    '</tpl>' +
                    (Ext.isIE ? ieArrow : arrow) +
                '</tpl>',
                {
                    getArrowDirection : function(coords) {
                        var lastXY = coords[coords.length-1];
                        if (lastXY.x1 === lastXY.x2) {
                            return 'down';
                        } else if (lastXY.x1 > lastXY.x2) {
                            return 'left';
                        } else {
                            return 'right';
                        }
                    }
                }
            ); 
        }
        this.lineTpl.compile();
    },

    getTaskBox : function(task) {
        var ts = this.taskStore;
        
        // Check if element is inside a collapsed parent task
        if (!task || !ts.isVisibleNode(task)) {
            return null;
        }

        var v = this.view,
            left = v.getXFromDate(task.get('StartDate'));
            
        if (left < 0) {
            return null;
        }

        var right = v.getXFromDate(task.get('EndDate')),
            taskIndex = ts.indexOf(task); // Store could be filtered

        if (right < 0 || taskIndex < 0) {
            return null;
        }

        var g = this.grid,
            el = Ext.get(v.getRow(taskIndex));

        if (!el) {
            return null;
        }

        var firstChild = el.first();
        
        el = firstChild || el;
            
        var xOffset = g.getXOffset(task),
            offsets = el.getOffsetsTo(v.scroller),
            top = offsets[1] + v.scroller.getScroll().top;
        
        left -= xOffset;
        right += xOffset - 1;

        return new Ext.lib.Region(top, right, top + el.getHeight(), left);
    },
    
    draw : function(dependencyRecord) {
        var me = this,
            ts = me.taskStore;
        
        // Normalize input
        if (!Ext.isArray(dependencyRecord)) {
            dependencyRecord = [dependencyRecord];
        }

        if (dependencyRecord.length === 0 || ts.getCount() <= 0) {
            return;
        }
        
        var depData = [],
            buffered = me.buffered,
            view = me.view,
            firstVis, lastVis, coords, fromTask, toTask, fromBox, toBox, r;

        if (buffered) {
            var visibleRows = view.getVisibleRows();
            firstVis = visibleRows.first;
            lastVis = visibleRows.last;
        }

        for (var i = 0, l = dependencyRecord.length; i < l; i++) {
            r = dependencyRecord[i];
            fromTask = ts.getById(r.get('From'));
            toTask = ts.getById(r.get('To'));
            
            if (!buffered || me.intersectsViewport(fromTask, toTask, firstVis, lastVis)) {
                
                fromBox = me.getTaskBox(fromTask);
                toBox = me.getTaskBox(toTask);
                
                if (fromBox && toBox) {
           
                    switch(r.get('Type')) {
                        case Sch.gantt.Dependency.StartToEnd:
                            coords = me.getStartToEndCoordinates(fromBox, toBox);
                        break;
            
                        case Sch.gantt.Dependency.StartToStart:
                            coords = me.getStartToStartCoordinates(fromBox, toBox);
                        break;
            
                        case Sch.gantt.Dependency.EndToStart:
                            coords = me.getEndToStartCoordinates(fromBox, toBox);
                        break;
            
                        case Sch.gantt.Dependency.EndToEnd:
                            coords = me.getEndToEndCoordinates(fromBox, toBox);
                        break;
            
                        default:
                            throw 'Invalid case statement';
                        break;
                    }
                    depData.push({
                        lineCoordinates : coords,
                        id : r.id
                    });
                }
            }
        }

        me.lineTpl.insertFirst(me.containerEl, depData);
    },

    intersectsViewport : function(task1, task2, firstVisibleRowIndex, lastVisibleRowIndex) {
        var index1 = this.taskStore.indexOf(task1),
            index2 = this.taskStore.indexOf(task2);

        return !((index1 < firstVisibleRowIndex && index2 < firstVisibleRowIndex) ||
                (index1 > lastVisibleRowIndex && index2 > lastVisibleRowIndex));
    },

    getStartToStartCoordinates : function(fromBox, toBox, firstVisibleRow, lastVisibleRow) {
        var x1 = fromBox.left, 
            y1 = fromBox.top + (fromBox.bottom - fromBox.top)/2, 
            x2 = toBox.left, 
            y2 = toBox.top + (toBox.bottom - toBox.top)/2, 
            y2offset = fromBox.top < toBox.top ? (y2 - this.yOffset - this.midRowOffset) : (y2 + this.yOffset + this.midRowOffset),
            leftPointOffset = this.xOffset + this.arrowOffset;
        
        if (x1 > (x2 + this.xOffset)) {
            leftPointOffset += (x1 - x2);
        }
        
        return [
            {
                x1 : x1, 
                y1 : y1, 
                x2 : x1 - leftPointOffset,
                y2 : y1
            },
            {
               x1 : x1 - leftPointOffset, 
               y1 : y1, 
               x2 : x1 - leftPointOffset, 
               y2 : y2
            },
            {
                x1 : x1 - leftPointOffset, 
                y1 : y2, 
                x2 : x2 - this.arrowOffset, 
                y2 : y2
            }
        ];
    },
    
    getStartToEndCoordinates : function(fromBox, toBox) {
        var x1 = fromBox.left, 
            y1 = fromBox.top + (fromBox.bottom - fromBox.top)/2, 
            x2 = toBox.right, 
            y2 = toBox.top + (toBox.bottom - toBox.top)/2, 
            y2offset = fromBox.top < toBox.top ? (y2 - this.yOffset - this.midRowOffset) : (y2 + this.yOffset + this.midRowOffset),
            coords,
            leftOffset;
        
         if (x2 > (x1 + this.xOffset - this.arrowOffset) ||
             Math.abs(x2 - x1) < (2*(this.xOffset + this.arrowOffset)))
         { 
            leftOffset = x1 - this.xOffset - this.arrowOffset;
            var x2Offset = x2 + this.xOffset + this.arrowOffset;

            // To after from
            // --|
            // |-----------
            //             |
            //          <--|
            coords = [
                {
                    x1 : x1, 
                    y1 : y1, 
                    x2 : leftOffset,
                    y2 : y1
                },
                {
                   x1 : leftOffset, 
                   y1 : y1, 
                   x2 : leftOffset, 
                   y2 : y2offset
                },
                {
                    x1 : leftOffset, 
                    y1 : y2offset, 
                    x2 : x2Offset, 
                    y2 : y2offset
                },
                {
                    x1 : x2Offset, 
                    y1 : y2offset, 
                    x2 : x2Offset, 
                    y2 : y2
                },
                {
                    x1 : x2Offset, 
                    y1 : y2, 
                    x2 : x2 + this.arrowOffset, 
                    y2 : y2
                }
            ];
         }
         else {
            // From after to
            //    -----|
            // <--|
            //     
            leftOffset = x1 - this.xOffset - this.arrowOffset;
            
            coords = [
                {
                    x1 : x1, 
                    y1 : y1, 
                    x2 : leftOffset,
                    y2 : y1
                },
                {
                   x1 : leftOffset, 
                   y1 : y1, 
                   x2 : leftOffset, 
                   y2 : y2
                },
                {
                    x1 : leftOffset, 
                    y1 : y2, 
                    x2 : x2 + this.arrowOffset, 
                    y2 : y2
                }
            ];
        }
        return coords;
    },
    
    getEndToStartCoordinates : function(fromBox, toBox) {
       
        var x1 = fromBox.right, 
            y1 = fromBox.top + (fromBox.bottom - fromBox.top)/2, 
            x2 = toBox.left, 
            y2 = toBox.top + (toBox.bottom - toBox.top)/2, 
            y2offset = fromBox.top < toBox.top ? (y2 - this.yOffset - this.midRowOffset) : (y2 + this.yOffset + this.midRowOffset),
            coords,
            leftOffset;
         
         if (x2 >= (x1 - 6) && y2 > y1) { 
            
            
            leftOffset = Math.max(x1 - 6, x2) + this.xOffset;
            y2 = toBox.top;
           
            coords = [
                {
                    x1 : x1, 
                    y1 : y1, 
                    x2 : leftOffset,
                    y2 : y1
                },
                {
                    x1 : leftOffset, 
                    y1 : y1, 
                    x2 : leftOffset, 
                    y2 : y2 - this.arrowOffset
                }
            ];
         }
         else {
                 
            leftOffset = x1 + this.xOffset + this.arrowOffset;
            var x2Offset = x2 - this.xOffset - this.arrowOffset;
            coords = [
                {
                    x1 : x1, 
                    y1 : y1, 
                    x2 : leftOffset,
                    y2 : y1
                },
                {
                   x1 : leftOffset, 
                   y1 : y1, 
                   x2 : leftOffset, 
                   y2 : y2offset
                },
                {
                    x1 : leftOffset, 
                    y1 : y2offset, 
                    x2 : x2Offset, 
                    y2 : y2offset
                },
                {
                    x1 : x2Offset, 
                    y1 : y2offset, 
                    x2 : x2Offset, 
                    y2 : y2
                },
                {
                    x1 : x2Offset, 
                    y1 : y2, 
                    x2 : x2 - this.arrowOffset, 
                    y2 : y2
                }
            ];
        }
        return coords;
    },
   
    getEndToEndCoordinates : function(fromBox, toBox) {
       
        var x1 = fromBox.right, 
            y1 = fromBox.top + (fromBox.bottom - fromBox.top)/2, 
            x2 = toBox.right + this.arrowOffset, 
            y2 = toBox.top + (toBox.bottom - toBox.top)/2, 
            rightPointOffset = x2 + this.xOffset + this.arrowOffset;
        
        if (x1 > (x2 + this.xOffset)) {
            rightPointOffset += x1 - x2;
        }
         
        return [
            {
                x1 : x1, 
                y1 : y1, 
                x2 : rightPointOffset,
                y2 : y1
            },
            {
               x1 : rightPointOffset, 
               y1 : y1, 
               x2 : rightPointOffset, 
               y2 : y2
            },
            {
                x1 : rightPointOffset, 
                y1 : y2, 
                x2 : x2, 
                y2 : y2
            }
        ];
    }
});


 
Ext.ns('Sch.gantt');


Sch.gantt.GanttPanel = Ext.extend(Sch.AbstractSchedulerPanel, {
    

     
    
    
    highlightWeekends : true,
    
    
    enableTaskDragDrop : true,
    
    
    enableDependencyDragDrop : true,
     
    
    enableProgressBarResize : false,

    
    toggleParentTasksOnClick : true,
    
    
    recalculateParentsAfterEdit: true,
    
    
    cascadeChanges: false,
     
   
    showTodayLine : false,
     
    

    
    allowParentTaskMove: false,

    

    
    eventRenderer : Ext.emptyFn,

    resizeHandles : 'both',
    
    
      
    
    
    
    
    
    
    
    
    
    getDependencyManager : function() {
        return this.dependencyManager;
    },
    
    
    disableWeekendHighlighting : function(disabled) {
        this.weekendZonesPlugin.setDisabled(disabled);
    },
    
    
      
    
    updateTimeColumnHeaderWidths : function(width) {
        var cm = this.getColumnModel();
        for (var i = cm.getLockedCount(), l = this.view.getLastTimeColumnIndex(); i < l; i++) {
	        cm.setColumnWidth(i, width, true);
	    }
	    
	    this.view.updateHeaders();
    },
    
    
    resolveTask : function(el) {
        var element = Ext.get(el);
        if (!element.is(this.eventWrapSelector)) {
            element = element.up(this.eventWrapSelector);
        }
        return this.getEventRecordFromDomId(element.child(this.eventSelector).id);
    },

    
    getEventRecordFromDomId : function(id) {
        var trueId = this.getEventIdFromDomNodeId(id);
        return this.taskStore.getById(trueId);
    },
    
    
    fitTimeColumns : function() {
        this.view.fitColumns(true);
        this.view.refresh(true);
    },
  
    // Simplified method which just returns the duration in days between two dates
    // private
    getDuration : function(start, end) {
        return Math.round(Sch.util.Date.getDurationInDays(start, end)*10)/10;
    },
    
    getTaskStore : function() {
        return this.taskStore;
    },

    getDependencyStore : function() {
        return this.dependencyStore;
    },

    // Number of pixels to offset a milestone diamond
    milestoneOffset : 8,
    
    // Number of pixels to offset a parent task 
    parentTaskOffset : 6,
    
    clicksToEdit : 1,
    
    columnLines : false,
    
    // private
    eventSelector : '.sch-gantt-item',
    
    eventWrapSelector : '.sch-event-wrap',
    
    cmpCls : 'sch-ganttpanel',

    
    dndValidatorFn : Ext.emptyFn,

    
    resizeValidatorFn : Ext.emptyFn,
    
    

    // private
    constructor : function(config) {
        this.addEvents(
            // Task click-events --------------------------
            
            
            'taskclick', 
            
            
            'taskdblclick', 
            
            
            'taskcontextmenu', 

            // Resizing events start --------------------------
            
            'beforeresize', 
            
            
            'resizestart', 
            
            
            'partialresize', 
            
            
            'afterresize',
            // Resizing events end --------------------------
            
            // Task progress bar resizing events start --------------------------
            
            'beforeprogressbarresize', 
            
            
            'progressbarresizestart', 
           
            
            'afterprogressbarresize',
            // Task progressbar resizing events end --------------------------
            
            // Dnd events start --------------------------
             
            'beforednd', 
            
            
            'dndstart',
            
            
            'drop',
            
            
            'afterdnd',
            // Dnd events end --------------------------
            
             
            'labeledit_beforestartedit', 

            
            'labeledit_beforecomplete', 
            
            
            'labeledit_complete',

            
            'dependencydblclick',

            // Dependency drag drop events --------------------------
            
            
            // Dependency drag drop end --------------------------
            
            'beforedependencydrag', 

             
            'dependencydragstart', 

            
            'dependencydrop', 

            
            'afterdependencydragdrop'
        );

        Sch.gantt.GanttPanel.superclass.constructor.call(this, config);
    },

    // private
    configureLabels : function() {
        this.leftLabelField = this.leftLabelField || {};
        this.rightLabelField = this.rightLabelField || {};
       
        var defaults = {
            renderer : function(v) {return v;},
            dataIndex : undefined
        };

        if (Ext.isString(this.leftLabelField)) {
            this.leftLabelField = {
                dataIndex : this.leftLabelField
            };
        }

        if (Ext.isString(this.rightLabelField)) {
            this.rightLabelField = {
                dataIndex : this.rightLabelField
            };
        }

        Ext.applyIf(this.leftLabelField, defaults);
        Ext.applyIf(this.rightLabelField, defaults);

        // Initialize left editor (if defined)
        if (this.leftLabelField.editor) {
            this.leftLabelField.editor = new Sch.gantt.LabelEditor(this, {
                alignment: 'r-r',
                field : this.leftLabelField.editor,
                delegate : '.sch-gantt-label-left',
                dataIndex : this.leftLabelField.dataIndex
            });
        }

        // Initialize right editor (if defined)
        if (this.rightLabelField.editor) {
            this.rightLabelField.editor = new Sch.gantt.LabelEditor(this, {
                alignment: 'l-l',
                field : this.rightLabelField.editor,
                delegate : '.sch-gantt-label-right',
                dataIndex : this.rightLabelField.dataIndex
            });
        }

        this.on('labeledit_beforestartedit', this.onBeforeLabelEdit, this);
    },

    setupTemplates: function() {

        var tplCfg = {
            enableDependencyDragDrop : this.enableDependencyDragDrop !== false, 
            resizeHandles : this.resizeHandles, 
            enableProgressBarResize : this.enableProgressBarResize,
            showBaseline : this.showBaseline
        };
        
        if (!this.eventTemplate) {
            this.eventTemplate = new Sch.gantt.TaskTemplate(tplCfg);
        }
        
        if (!this.parentEventTemplate) {
            this.parentEventTemplate = new Sch.gantt.ParentTaskTemplate(tplCfg);
        }
    
        if (!this.milestoneTemplate) {
            this.milestoneTemplate = new Sch.gantt.MilestoneTemplate(tplCfg);
        }
    },
    
    // private 
    configureFunctionality : function() {
        // Normalize to array
        if (this.plugins && !Ext.isArray(this.plugins)) {
            this.plugins = [this.plugins];
        } else {
            this.plugins = this.plugins || [];
        }
        
        if (this.enableProgressBarResize !== false && Sch.gantt.ProgressBarResize) {
            this.progressBarResizer = new Sch.gantt.ProgressBarResize(this);
            this.on('beforeprogressbarresize', this.onBeforeTaskProgressBarResize, this);
        }

        if (this.resizeHandles !== 'none' && Sch.gantt.Resize) {
            this.taskResizer = new Sch.gantt.Resize(this, Ext.apply({
                validatorFn : this.resizeValidatorFn || Ext.emptyFn,
                validatorFnScope : this.validatorFnScope || this
            }, this.resizeConfig || {}));

            this.on('beforeresize', this.onBeforeTaskResize, this);
        }
        
        if (this.enableTaskDragDrop  && Sch.gantt.DragDrop) {
            this.taskDragDrop = new Sch.gantt.DragDrop(this, Ext.apply({
                validatorFn : this.dndValidatorFn  || Ext.emptyFn,
                validatorFnScope : this.validatorFnScope || this
            }, this.dragDropConfig));
            
            this.on('beforednd', this.onBeforeTaskDrag, this);
        }
        
        if (this.highlightWeekends) {
            this.weekendZonesPlugin = new Sch.plugins.Zones({
                store : new Ext.data.JsonStore({
                    fields : ['StartDate', 'EndDate']
                })
            });
            this.plugins.push(this.weekendZonesPlugin);
        }
        
        if (this.showTodayLine) {
            this.todayLinePlugin = new Sch.plugins.CurrentTimeLine();
            this.plugins.push(this.todayLinePlugin);
        }

        this.on('beforeedit', this.onBeforeEdit, this);
    },
    
    // private
    onBeforeTaskDrag : function(p, record) {
        return !this.readOnly && (this.allowParentTaskMove || this.taskStore.isLeafNode(record));
    },

    // private
    onBeforeTaskResize : function() {
        return !this.readOnly;
    },

    onBeforeTaskProgressBarResize : function() {
        return !this.readOnly;
    },

    onBeforeLabelEdit : function() {
        return !this.readOnly;
    },

    onBeforeEdit : function() {
        return !this.readOnly;
    },

    // private
    isMilestone : function(task) {
        return task.get('EndDate') - task.get('StartDate') === 0;
    },
    
    // private
    populateWeekendZonesPlugin : function() {
        var data = [],
            gStart = this.getStart(),
            gEnd = this.getEnd(),
            c = gStart.clone();
        
        while(c.getDay() !== 6) {
            c = Sch.util.Date.add(c, Sch.util.Date.DAY, 1);
        }
        
        while (c < gEnd) {
            data.push({
                StartDate : c,
                EndDate : c.add(Sch.util.Date.DAY, 2)
            });
            c = Sch.util.Date.add(c, Sch.util.Date.WEEK, 1);
        }
        
        this.weekendZonesPlugin.store.loadData(data);
    },
    
    // private
    getView : function(){
        if(!this.view){
            this.viewConfig = this.viewConfig || {};
            Ext.applyIf(this.viewConfig, {
                cellSelectorDepth : 22,
                rowSelectorDepth : 12
            });
            this.view = new Sch.gantt.GanttView(this.viewConfig);
        }
        return this.view;
    },
    
    // private
    initComponent : function() {
        this.configureLabels();
        this.setupTemplates();

        // Backwards compatibility
        var taskStore = this.taskStore || this.store;
        
        Ext.apply(this, {
            store : taskStore,          // For the grid panel API
            taskStore : taskStore
        });

        if (this.highlightWeekends) {
            this.on('viewchange', this.populateWeekendZonesPlugin, this);
        }
        
        if (!this.dependencyStore) {
            // Assign an empty store if one hasn't been provided
            this.dependencyStore = new Ext.data.Store();
        }
        
        if(!this.selModel && !this.disableSelection){
            this.selModel = new Sch.gantt.RowSelectionModel();
        }
        
        this.setupGanttEvents();
        this.configureFunctionality();

        Sch.gantt.GanttPanel.superclass.initComponent.call(this);
    },
    
    // private
    setupGanttEvents : function() {
        var sm = this.getSelectionModel();

        this.on({
             rowclick : function(g, rowIndex, e) {
                var t = e.getTarget(this.eventSelector);
                if (t) {
                    var task = this.taskStore.getAt(rowIndex);
                    if (!this.toggleParentTasksOnClick || !!this.taskStore.isLeafNode(task)) {
                        Ext.grid.RowSelectionModel.prototype.handleMouseDown.call(sm, this, rowIndex, e);
                    }
                    this.fireEvent('taskclick', this, task, t, e);
                }
            },
            rowdblclick : function(g, rowIndex, e) {
                var t = e.getTarget(this.eventSelector);
                if (t) {
                    this.fireEvent('taskdblclick', this, this.taskStore.getAt(rowIndex), e);
                }
            },
            rowcontextmenu : function(g, rowIndex, e) {
                var t = e.getTarget(this.eventSelector);
                if (t) {
                    this.fireEvent('taskcontextmenu', this, this.taskStore.getAt(rowIndex), e);
                }
            },
            scope : this
        });

        if (this.recalculateParentsAfterEdit) {
            this.taskStore.on({
                'update' : this.onStoreUpdate, 
                'add' : this.onStoreAddRemove, 
                'remove' : this.onStoreAddRemove, 
                scope : this
            });
        }
    },

    // private
    onDragDropStart : function() {
        if (this.dragCreatePlug) {
            this.dragCreatePlug.setDisabled(true);
        }
        
        if (this.tip) {
            this.tip.hide();
            this.tip.disable();
        }
    },
    
    // private
    onDragDropEnd : function() {
        if (this.dragCreatePlug) {
            this.dragCreatePlug.setDisabled(false);
        }
        
        if (this.tip) {
            this.tip.enable();
        }
    },
    
    // private
    afterRender : function() {    
        this.addClass([
            (this.highlightWeekends ? 'sch-ganttpanel-highlightweekends' : ''),
            (this.showBaseline ? 'sch-ganttpanel-showbaseline' : '')
        ].join(' '));
        
        this.initDependencies(); 

        Sch.gantt.GanttPanel.superclass.afterRender.apply(this, arguments);
    },
    
    // private
    initDependencies : function() {
        if (this.dependencyStore) {
            var v = this.getView(),
                dm = new Sch.gantt.DependencyManager(this, {
                    yOffset : this.showBaseline ? 3 : 7,
                    containerEl : v.scroller,
                    checkVisible : true,
                    enableDependencyDragDrop : this.enableDependencyDragDrop,
                    store : this.dependencyStore
                });
        
            dm.on({
                beforednd : this.onBeforeDependencyDrag, 
                dndstart : this.onDependencyDragStart, 
                drop : this.onDependencyDrop, 
                afterdnd : this.onAfterDependencyDragDrop,
                beforecascade : this.onBeforeCascade,
                cascade : this.onCascade,
                scope : this
            }); 

            this.dependencyManager = dm;
            
            this.mon(v.scroller, 'dblclick', function(e, t) {
                var rec = dm.getRecordForDependencyEl(t);
                this.fireEvent('dependencydblclick', this, rec, e, t);
            }, 
            this,
            {
                delegate : '.sch-dependency'
            });
        }
    },

    // private
    onBeforeDependencyDrag: function(dm, sourceTask) {
        return this.fireEvent('beforedependencydrag', this, sourceTask);
    },

    // private
    onDependencyDragStart : function(dm) {
        this.fireEvent('dependencydragstart', this);
        if (this.tip) {
            this.tip.disable();
        }
    },

    onDependencyDrop : function(dm, fromId, toId, type) {
        this.fireEvent('dependencydrop', this, this.taskStore.getById(fromId), this.taskStore.getById(toId), type);
    },

    // private
    onAfterDependencyDragDrop : function() {
        this.fireEvent('afterdependencydragdrop', this);
        
        // Enable tooltip after drag again
        if (this.tip) {
            this.tip.enable();
        }
    },
    
    // Disconnect the store 'update' listener for the view
    // private
    onBeforeCascade : function(dm, r) {
        var v = this.view;
        this.taskStore.un('update', v.onUpdate, v);
    },

    // Reconnect the store 'update' listener for the view
    // private
    onCascade : function(dm, r) {
        var v = this.view;
        this.taskStore.on('update', v.onUpdate, v);
    },

    
    getLeftEditor : function() {
        return this.leftLabelField.editor;
    },

    
    getRightEditor : function() {
        return this.rightLabelField.editor;
    },

     
    editLeftLabel : function(record) {
        var le = this.getLeftEditor();
        if (le) {
            le.edit(record);
        }
    },
    
    
    editRightLabel : function(record) {
        var re = this.getRightEditor();
        if (re) {
            re.edit(record);
        }
    },
    
    getDependenciesForTask : function(record) {
        return this.dependencyManager.getDependenciesForTask(record);
    },
    
    
    highlightDependency : function(record) {
        this.dependencyManager.highlightDependency(record);
    },
    
    
    unhighlightDependency : function(record) {
        this.dependencyManager.unhighlightDependency(record);
    },
    
    // private
    getTaskById : function(id) {
        return this.taskStore.getById(id);
    },
    
    
    highlightTask : function(taskId, highlightDependentTasks) {
        if (taskId instanceof Ext.data.Record) {
            taskId = taskId.id;
        }
         
        var index = this.taskStore.indexOfId(taskId);
       
        // El might not exist in DOM
        if (index >= 0) {
            this.getSelectionModel().selectRow(index, true);
        }
        
        if (highlightDependentTasks !== false) {
            this.dependencyStore.queryBy(function(dep) {
                if (dep.get('From') == taskId) {
                    this.highlightDependency(dep.id);
                    this.highlightTask(dep.get('To'), highlightDependentTasks);
                }
            }, this);
        }
    },
    
    // private
    clearSelectedTasksAndDependencies : function() {
        this.getSelectionModel().clearSelections();
        this.getView().el.select('.sch-dependency-selected').removeClass('sch-dependency-selected');
    },
    
    
    
    master_column_id : 0,

    // private
    onClick : function(e) {
        var target = e.getTarget('.ux-maximgb-tg-elbow-active', 1) || 
                     (this.toggleParentTasksOnClick && e.getTarget('.sch-gantt-parenttask-bar'));
        
        // Row click
        if (target) {
            var view = this.getView(),
                row = view.findRowIndex(target),
                store = this.getTaskStore(),
                record = store.getAt(row);
                
            if (store.isExpandedNode(record)) {
                store.collapseNode(record);
            } else {
                store.expandNode(record);
            }
        } 
        if (!target || target.className.match('sch-gantt-parenttask-bar')) {
            Sch.gantt.GanttPanel.superclass.onClick.call(this, e);
        }
    },

    // private
    onMouseDown : function(e) {
        if (!e.getTarget('.ux-maximgb-tg-elbow-active', 1)) {
            Sch.gantt.GanttPanel.superclass.onMouseDown.call(this, e);
        }
    },
    
    // private
    onStoreAddRemove : function(store, rec) {
        this.taskStore.suspendEvents(true);
        this.recalculateParents(rec);
        this.taskStore.resumeEvents();
    },
    
    // private
    onStoreUpdate : function(store, rec, operation, prev) {
        // No need to recalculate for commits
        if (operation !== Ext.data.Record.COMMIT && (!prev || prev.StartDate || prev.EndDate)) {
            this.onStoreAddRemove(store, rec);
        }
    },
    
    // Recalculates parent start/end dates
    // private
    recalculateParents : function(rec) {
        rec = Ext.isArray(rec) ? rec[0] : rec;
        var earliest = new Date(9999,0,0), 
            latest = new Date(0),
            parent = this.taskStore.getNodeParent(rec);
        
        if (parent) {
            var children = this.taskStore.getNodeChildren(parent),
                tStart, tEnd;

            if (children.length > 0) {
                Ext.each(children, function(r) {
                    tStart = r.get('StartDate');
                    tEnd = r.get('EndDate');
                    
                    earliest = Sch.util.Date.min(earliest, tStart || earliest);
                    latest = Sch.util.Date.max(latest, tEnd || latest);
                });
                
                if (parent.get('StartDate') - earliest !== 0) {
                    parent.set('StartDate', earliest);
                }
                if (parent.get('EndDate') - latest !== 0) {
                    parent.set('EndDate', latest);
                }
            }
            
            if (this.cascadeChanges) {
                this.getDependencyManager().performCascade(parent);
            }
            
            this.recalculateParents(parent);
        }
    },
    
    
    getCriticalPaths : function() {
        return this.getDependencyManager().getCriticalPaths();
    },

     
    highlightCriticalPaths : function() {
        // First clear any selected tasks/dependencies
        this.clearSelectedTasksAndDependencies();
        
        var paths = this.getCriticalPaths(),
            dm = this.getDependencyManager(),
            ds = this.dependencyStore,
            t,i,l, depRecord;
        
        Ext.each(paths, function(tasks) {
            for (i = 0, l = tasks.length; i < l; i++) {
                t = tasks[i];
                this.highlightTask(t, false);
                
                if (i < (l - 1)) {
                    depRecord = ds.getAt(ds.findBy(function(dep) { return dep.get('To') === t.id && dep.get('From') === tasks[i+1].id; }));
                    dm.highlightDependency(depRecord);
                }
            }
        }, this);
        
        this.el.addClass('sch-gantt-critical-chain');
    },
    
    
    unhighlightCriticalPaths : function() {
        this.el.removeClass('sch-gantt-critical-chain');
        this.clearSelectedTasksAndDependencies();
    },

    //private
    onDestroy : function() {
        this.dependencyManager.destroy();
        Sch.gantt.GanttPanel.superclass.onDestroy.apply(this, arguments);
    },

    //private
    getXOffset : function(task) {
        var offset = 0;
        
        if (this.isMilestone(task)) {
            offset = this.milestoneOffset;
        } else if (!this.taskStore.isLeafNode(task)) {
            offset = this.parentTaskOffset;
        }

        return offset;
    },

    //private
    stopEditing : function() {
        Sch.gantt.GanttPanel.superclass.stopEditing.apply(this, arguments);

        var lEd = this.getLeftEditor(),
            rEd = this.getRightEditor();
        if (lEd) {
            lEd.completeEdit();
        }

        if (rEd) {
            rEd.completeEdit();
        }
    },

    zoomToFit : function() {
       // TODO
    },

    //private
   isCellEditable : function(colIndex, rowIndex) {
        var dataIndex = this.getColumnModel().getDataIndex(colIndex),
            ganttEditable = this.taskStore.isLeafNode(this.taskStore.getAt(rowIndex)) || (dataIndex !== 'Duration' && dataIndex !== 'StartDate' && dataIndex !== 'EndDate');
       
        return ganttEditable;
    },


    // private
    // Overridden to only walked locked cells
    walkCells : function(row, col, step, fn, scope){
        var cm    = this.colModel,
            clen  = cm.getLockedCount(),
            ds    = this.taskStore,
            rlen  = ds.getCount(),
            first = true;

        if(step < 0){
            if(col < 0){
                row--;
                first = false;
            }
            while(row >= 0){
                if(!first){
                    col = clen-1;
                }
                first = false;
                while(col >= 0){
                    if(fn.call(scope || this, row, col, cm) === true){
                        return [row, col];
                    }
                    col--;
                }
                row--;
            }
        } else {
            if(col >= clen){
                row++;
                first = false;
            }
            while(row < rlen){
                if(!first){
                    col = 0;
                }
                first = false;
                while(col < clen){
                    if(fn.call(scope || this, row, col, cm) === true){
                        return [row, col];
                    }
                    col++;
                }
                row++;
            }
        }
        return null;
    },

    getTooltipRecord : function(el) {
        return this.resolveTask(el);
    }
}); 

Ext.reg('gantt', Sch.gantt.GanttPanel);




Ext.ns('Sch.gantt');


Sch.gantt.ProgressBarResize = function (gantt) {
    this.gantt = gantt;

    this.gantt.on({
        afterrender: this.onGanttRender,
        destroy: this.cleanUp,
        scope: this
    });
    Sch.gantt.ProgressBarResize.superclass.constructor.call(this);
};

Ext.extend(Sch.gantt.ProgressBarResize, Ext.util.Observable, {
    
    useTooltip: true,

    
    increment: 10,

    // private
    onGanttRender: function () {
        var g = this.gantt;

        g.mon(g.getView().mainBody, 'mousedown', this.onMouseDown, this, { delegate: '.sch-gantt-progressbar-handle' });
    },

    onMouseDown: function (e, t) {
        var g = this.gantt,
            rec = g.resolveTask(t);

        if (g.fireEvent('beforeprogressbarresize', g, rec)) {
            var progBar = Ext.fly(e.getTarget('.sch-gantt-item')).child('.sch-gantt-progress-bar');
            e.stopEvent();
            
            this.createResizable(progBar, rec, e);
            g.fireEvent('progressbarresizestart', g, rec);
        }
    },

    // private
    createResizable: function (el, taskRecord, e) {

        var t = e.getTarget(),
            taskEl = el.up(this.gantt.eventSelector),
            taskWidth = taskEl.getWidth() - (Ext.isBorderBox ? 4 : 2),
            widthIncrement = taskWidth * this.increment / 100;

        var resizable = new Sch.LazyResizable(el, {
            taskRecord: taskRecord,
            taskWidth: taskWidth,
            handles: 'e',
            dynamic: true,
            widthIncrement: widthIncrement,
            minWidth: 0,
            maxWidth: taskWidth,
            listeners: {
                partialresize: { fn: this.partialResize, scope: this },
                resize: { fn: this.afterResize, scope: this }
            }
        },
            'east',
            e
        );

        resizable.proxy.setStyle('border', '0 none');

        if (this.useTooltip) {
            if (!this.tip) {
                this.tip = new Ext.QuickTip({
                    autoHide: false,
                    anchor: 'b',
                    anchorToTarget: true,
                    html: '%'
                });
            }
            this.tip.initTarget(el);

            this.tip.show();
            this.tip.body.update(taskRecord.get('PercentDone') + '%');
        }
    },

    // private
    partialResize: function (r, newWidth) {
        var percent = Math.round(newWidth * 100 / (r.taskWidth * this.increment)) * this.increment;

        if (this.useTooltip) {
            this.tip.body.update(percent + '%');
        }
    },

    // private
    afterResize: function (r, w, h, e) {
        this.gantt.fireEvent('afterprogressbarresize', this.gantt, r.taskRecord);
        
        if (this.useTooltip) {
            this.tip.hide();
        }

        var percent = Math.round(w * 100 / r.taskWidth);

        r.taskRecord.set('PercentDone', percent);

        // Destroy resizable 
        r.destroy();
    },

    cleanUp: function () {
        if (this.tip) {
            this.tip.destroy();
        }
    }
}); 

